| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <classpath> | |
| 3 | <classpathentry kind="src" path="src/main/java"> | |
| 4 | <attributes> | |
| 5 | <attribute name="FROM_GRADLE_MODEL" value="true"/> | |
| 6 | </attributes> | |
| 7 | </classpathentry> | |
| 8 | <classpathentry kind="src" path="src/main/resources"> | |
| 9 | <attributes> | |
| 10 | <attribute name="FROM_GRADLE_MODEL" value="true"/> | |
| 11 | </attributes> | |
| 12 | </classpathentry> | |
| 13 | <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> | |
| 14 | <accessrules> | |
| 15 | <accessrule kind="accessible" pattern="javafx/**"/> | |
| 16 | </accessrules> | |
| 17 | </classpathentry> | |
| 18 | <classpathentry exported="true" kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> | |
| 19 | <classpathentry kind="output" path="bin"/> | |
| 20 | </classpath> | |
| 1 | 21 |
| 1 | # always use LF line endings | |
| 2 | ||
| 3 | # ALL FILES: | |
| 4 | # Normalize line endings to LF on checkin and | |
| 5 | # prevent conversion to CRLF when the file is checked out. | |
| 6 | ||
| 7 | * text eol=lf | |
| 8 | ||
| 9 | ||
| 10 | # BINARY FILES: | |
| 11 | # Disable line ending normalize on checkin. | |
| 12 | ||
| 13 | *.gif binary | |
| 14 | *.jar binary | |
| 15 | *.png binary | |
| 16 | *.zip binary | |
| 1 | 17 |
| 1 | /bin/ | |
| 2 | /build/ | |
| 3 | /.gradle/ | |
| 4 | /gradle/ | |
| 5 | /.nb-gradle | |
| 6 | /private | |
| 7 | .nb-gradle-properties | |
| 8 | scrivenvar.pro | |
| 1 | 9 |
| 1 | workspace.xml | |
| 1 | 2 |
| 1 | ||
| 1 | <component name="ProjectCodeStyleConfiguration"> | |
| 2 | <state> | |
| 3 | <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> | |
| 4 | </state> | |
| 5 | </component> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="CompilerConfiguration"> | |
| 4 | <bytecodeTargetLevel target="11" /> | |
| 5 | </component> | |
| 6 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="GradleMigrationSettings" migrationVersion="1" /> | |
| 4 | <component name="GradleSettings"> | |
| 5 | <option name="linkedExternalProjectsSettings"> | |
| 6 | <GradleProjectSettings> | |
| 7 | <option name="distributionType" value="LOCAL" /> | |
| 8 | <option name="externalProjectPath" value="$PROJECT_DIR$" /> | |
| 9 | <option name="gradleHome" value="/opt/gradle" /> | |
| 10 | <option name="gradleJvm" value="#JAVA_HOME" /> | |
| 11 | <option name="modules"> | |
| 12 | <set> | |
| 13 | <option value="$PROJECT_DIR$" /> | |
| 14 | </set> | |
| 15 | </option> | |
| 16 | </GradleProjectSettings> | |
| 17 | </option> | |
| 18 | </component> | |
| 19 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RemoteRepositoriesConfiguration"> | |
| 4 | <remote-repository> | |
| 5 | <option name="id" value="central" /> | |
| 6 | <option name="name" value="Maven Central repository" /> | |
| 7 | <option name="url" value="https://repo1.maven.org/maven2" /> | |
| 8 | </remote-repository> | |
| 9 | <remote-repository> | |
| 10 | <option name="id" value="jboss.community" /> | |
| 11 | <option name="name" value="JBoss Community repository" /> | |
| 12 | <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> | |
| 13 | </remote-repository> | |
| 14 | <remote-repository> | |
| 15 | <option name="id" value="MavenRepo" /> | |
| 16 | <option name="name" value="MavenRepo" /> | |
| 17 | <option name="url" value="https://repo.maven.apache.org/maven2/" /> | |
| 18 | </remote-repository> | |
| 19 | <remote-repository> | |
| 20 | <option name="id" value="maven" /> | |
| 21 | <option name="name" value="maven" /> | |
| 22 | <option name="url" value="https://oss.sonatype.org/content/repositories/snapshots/" /> | |
| 23 | </remote-repository> | |
| 24 | <remote-repository> | |
| 25 | <option name="id" value="BintrayJCenter" /> | |
| 26 | <option name="name" value="BintrayJCenter" /> | |
| 27 | <option name="url" value="https://jcenter.bintray.com/" /> | |
| 28 | </remote-repository> | |
| 29 | <remote-repository> | |
| 30 | <option name="id" value="maven2" /> | |
| 31 | <option name="name" value="maven2" /> | |
| 32 | <option name="url" value="https://nexus.bedatadriven.com/content/groups/public" /> | |
| 33 | </remote-repository> | |
| 34 | </component> | |
| 35 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="ExternalStorageConfigurationManager" enabled="true" /> | |
| 4 | <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="14" project-jdk-type="JavaSDK"> | |
| 5 | <output url="file://$PROJECT_DIR$/out" /> | |
| 6 | </component> | |
| 7 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RGraphicsSettings"> | |
| 4 | <option name="height" value="1600" /> | |
| 5 | <option name="resolution" value="112" /> | |
| 6 | <option name="version" value="1" /> | |
| 7 | <option name="width" value="2560" /> | |
| 8 | </component> | |
| 9 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RSettings"> | |
| 4 | <option name="interpreterPath" value="/usr/bin/R" /> | |
| 5 | </component> | |
| 6 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RPackageService"> | |
| 4 | <option name="enabledRepositoryUrls"> | |
| 5 | <list> | |
| 6 | <option value="@CRAN@" /> | |
| 7 | </list> | |
| 8 | </option> | |
| 9 | </component> | |
| 10 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <module type="JAVA_MODULE" version="4" /> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="Palette2"> | |
| 4 | <group name="Swing"> | |
| 5 | <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 6 | <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> | |
| 7 | </item> | |
| 8 | <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 9 | <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> | |
| 10 | </item> | |
| 11 | <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 12 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> | |
| 13 | </item> | |
| 14 | <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> | |
| 15 | <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> | |
| 16 | </item> | |
| 17 | <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 18 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> | |
| 19 | <initial-values> | |
| 20 | <property name="text" value="Button" /> | |
| 21 | </initial-values> | |
| 22 | </item> | |
| 23 | <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 24 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | |
| 25 | <initial-values> | |
| 26 | <property name="text" value="RadioButton" /> | |
| 27 | </initial-values> | |
| 28 | </item> | |
| 29 | <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 30 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | |
| 31 | <initial-values> | |
| 32 | <property name="text" value="CheckBox" /> | |
| 33 | </initial-values> | |
| 34 | </item> | |
| 35 | <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 36 | <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> | |
| 37 | <initial-values> | |
| 38 | <property name="text" value="Label" /> | |
| 39 | </initial-values> | |
| 40 | </item> | |
| 41 | <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 42 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 43 | <preferred-size width="150" height="-1" /> | |
| 44 | </default-constraints> | |
| 45 | </item> | |
| 46 | <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 47 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 48 | <preferred-size width="150" height="-1" /> | |
| 49 | </default-constraints> | |
| 50 | </item> | |
| 51 | <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 52 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 53 | <preferred-size width="150" height="-1" /> | |
| 54 | </default-constraints> | |
| 55 | </item> | |
| 56 | <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 57 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 58 | <preferred-size width="150" height="50" /> | |
| 59 | </default-constraints> | |
| 60 | </item> | |
| 61 | <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 62 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 63 | <preferred-size width="150" height="50" /> | |
| 64 | </default-constraints> | |
| 65 | </item> | |
| 66 | <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 67 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 68 | <preferred-size width="150" height="50" /> | |
| 69 | </default-constraints> | |
| 70 | </item> | |
| 71 | <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 72 | <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> | |
| 73 | </item> | |
| 74 | <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 75 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 76 | <preferred-size width="150" height="50" /> | |
| 77 | </default-constraints> | |
| 78 | </item> | |
| 79 | <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 80 | <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> | |
| 81 | <preferred-size width="150" height="50" /> | |
| 82 | </default-constraints> | |
| 83 | </item> | |
| 84 | <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 85 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 86 | <preferred-size width="150" height="50" /> | |
| 87 | </default-constraints> | |
| 88 | </item> | |
| 89 | <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 90 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | |
| 91 | <preferred-size width="200" height="200" /> | |
| 92 | </default-constraints> | |
| 93 | </item> | |
| 94 | <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 95 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | |
| 96 | <preferred-size width="200" height="200" /> | |
| 97 | </default-constraints> | |
| 98 | </item> | |
| 99 | <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 100 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | |
| 101 | </item> | |
| 102 | <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 103 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | |
| 104 | </item> | |
| 105 | <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 106 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> | |
| 107 | </item> | |
| 108 | <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 109 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> | |
| 110 | </item> | |
| 111 | <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 112 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> | |
| 113 | <preferred-size width="-1" height="20" /> | |
| 114 | </default-constraints> | |
| 115 | </item> | |
| 116 | <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 117 | <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> | |
| 118 | </item> | |
| 119 | <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 120 | <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> | |
| 121 | </item> | |
| 122 | </group> | |
| 123 | </component> | |
| 124 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="VcsDirectoryMappings"> | |
| 4 | <mapping directory="$PROJECT_DIR$" vcs="Git" /> | |
| 5 | </component> | |
| 6 | </project> |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <projectDescription> | |
| 3 | <name>Markdown Writer FX</name> | |
| 4 | <comment></comment> | |
| 5 | <projects> | |
| 6 | </projects> | |
| 7 | <buildSpec> | |
| 8 | <buildCommand> | |
| 9 | <name>org.eclipse.jdt.core.javabuilder</name> | |
| 10 | <arguments> | |
| 11 | </arguments> | |
| 12 | </buildCommand> | |
| 13 | <buildCommand> | |
| 14 | <name>org.eclipse.buildship.core.gradleprojectbuilder</name> | |
| 15 | <arguments> | |
| 16 | </arguments> | |
| 17 | </buildCommand> | |
| 18 | </buildSpec> | |
| 19 | <natures> | |
| 20 | <nature>org.eclipse.jdt.core.javanature</nature> | |
| 21 | <nature>org.eclipse.buildship.core.gradleprojectnature</nature> | |
| 22 | </natures> | |
| 23 | <filteredResources> | |
| 24 | <filter> | |
| 25 | <id>1438449113801</id> | |
| 26 | <name></name> | |
| 27 | <type>26</type> | |
| 28 | <matcher> | |
| 29 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 30 | <arguments>1.0-projectRelativePath-matches-false-false-build</arguments> | |
| 31 | </matcher> | |
| 32 | </filter> | |
| 33 | <filter> | |
| 34 | <id>1438449113801</id> | |
| 35 | <name></name> | |
| 36 | <type>26</type> | |
| 37 | <matcher> | |
| 38 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 39 | <arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments> | |
| 40 | </matcher> | |
| 41 | </filter> | |
| 42 | </filteredResources> | |
| 43 | </projectDescription> | |
| 1 | 44 |
| 1 | GRADLE_BUILD_COMMANDS=org.eclipse.jdt.core.javabuilder | |
| 2 | GRADLE_NATURES=org.eclipse.jdt.core.javanature | |
| 3 | build.commands=org.eclipse.jdt.core.javabuilder | |
| 4 | connection.arguments= | |
| 5 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) | |
| 6 | connection.gradle.user.home=null | |
| 7 | connection.java.home=null | |
| 8 | connection.jvm.arguments= | |
| 9 | connection.project.dir= | |
| 10 | derived.resources=.gradle,build | |
| 11 | eclipse.preferences.version=1 | |
| 12 | natures=org.eclipse.jdt.core.javanature | |
| 13 | project.path=\: | |
| 1 | 14 |
| 1 | eclipse.preferences.version=1 | |
| 2 | line.separator=\n | |
| 1 | 3 |
| 1 | eclipse.preferences.version=1 | |
| 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled | |
| 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 | |
| 4 | org.eclipse.jdt.core.compiler.compliance=1.8 | |
| 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error | |
| 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error | |
| 7 | org.eclipse.jdt.core.compiler.source=1.8 | |
| 1 | 8 |
| 1 | eclipse.preferences.version=1 | |
| 2 | editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true | |
| 3 | org.eclipse.jdt.ui.ignorelowercasenames=true | |
| 4 | org.eclipse.jdt.ui.importorder=java;javafx;javax;org;com; | |
| 5 | org.eclipse.jdt.ui.ondemandthreshold=99 | |
| 6 | org.eclipse.jdt.ui.staticondemandthreshold=99 | |
| 7 | sp_cleanup.add_default_serial_version_id=true | |
| 8 | sp_cleanup.add_generated_serial_version_id=false | |
| 9 | sp_cleanup.add_missing_annotations=true | |
| 10 | sp_cleanup.add_missing_deprecated_annotations=true | |
| 11 | sp_cleanup.add_missing_methods=false | |
| 12 | sp_cleanup.add_missing_nls_tags=false | |
| 13 | sp_cleanup.add_missing_override_annotations=true | |
| 14 | sp_cleanup.add_missing_override_annotations_interface_methods=true | |
| 15 | sp_cleanup.add_serial_version_id=false | |
| 16 | sp_cleanup.always_use_blocks=true | |
| 17 | sp_cleanup.always_use_parentheses_in_expressions=false | |
| 18 | sp_cleanup.always_use_this_for_non_static_field_access=false | |
| 19 | sp_cleanup.always_use_this_for_non_static_method_access=false | |
| 20 | sp_cleanup.convert_functional_interfaces=false | |
| 21 | sp_cleanup.convert_to_enhanced_for_loop=false | |
| 22 | sp_cleanup.correct_indentation=false | |
| 23 | sp_cleanup.format_source_code=false | |
| 24 | sp_cleanup.format_source_code_changes_only=false | |
| 25 | sp_cleanup.insert_inferred_type_arguments=false | |
| 26 | sp_cleanup.make_local_variable_final=false | |
| 27 | sp_cleanup.make_parameters_final=false | |
| 28 | sp_cleanup.make_private_fields_final=true | |
| 29 | sp_cleanup.make_type_abstract_if_missing_method=false | |
| 30 | sp_cleanup.make_variable_declarations_final=true | |
| 31 | sp_cleanup.never_use_blocks=false | |
| 32 | sp_cleanup.never_use_parentheses_in_expressions=true | |
| 33 | sp_cleanup.on_save_use_additional_actions=true | |
| 34 | sp_cleanup.organize_imports=false | |
| 35 | sp_cleanup.qualify_static_field_accesses_with_declaring_class=false | |
| 36 | sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true | |
| 37 | sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true | |
| 38 | sp_cleanup.qualify_static_member_accesses_with_declaring_class=false | |
| 39 | sp_cleanup.qualify_static_method_accesses_with_declaring_class=false | |
| 40 | sp_cleanup.remove_private_constructors=true | |
| 41 | sp_cleanup.remove_redundant_type_arguments=true | |
| 42 | sp_cleanup.remove_trailing_whitespaces=true | |
| 43 | sp_cleanup.remove_trailing_whitespaces_all=true | |
| 44 | sp_cleanup.remove_trailing_whitespaces_ignore_empty=false | |
| 45 | sp_cleanup.remove_unnecessary_casts=true | |
| 46 | sp_cleanup.remove_unnecessary_nls_tags=false | |
| 47 | sp_cleanup.remove_unused_imports=true | |
| 48 | sp_cleanup.remove_unused_local_variables=false | |
| 49 | sp_cleanup.remove_unused_private_fields=true | |
| 50 | sp_cleanup.remove_unused_private_members=false | |
| 51 | sp_cleanup.remove_unused_private_methods=true | |
| 52 | sp_cleanup.remove_unused_private_types=true | |
| 53 | sp_cleanup.sort_members=false | |
| 54 | sp_cleanup.sort_members_all=false | |
| 55 | sp_cleanup.use_anonymous_class_creation=false | |
| 56 | sp_cleanup.use_blocks=false | |
| 57 | sp_cleanup.use_blocks_only_for_return_and_throw=false | |
| 58 | sp_cleanup.use_lambda=true | |
| 59 | sp_cleanup.use_parentheses_in_expressions=false | |
| 60 | sp_cleanup.use_this_for_non_static_field_access=false | |
| 61 | sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true | |
| 62 | sp_cleanup.use_this_for_non_static_method_access=false | |
| 63 | sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true | |
| 64 | sp_cleanup.use_type_arguments=false | |
| 1 | 65 |
| 1 | language: java | |
| 2 | ||
| 3 | jdk: | |
| 4 | - oraclejdk8 | |
| 5 | ||
| 6 | # enable Java 8u45+, see https://github.com/travis-ci/travis-ci/issues/4042 | |
| 7 | addons: | |
| 8 | apt: | |
| 9 | packages: | |
| 10 | - oracle-java8-installer | |
| 11 | os: | |
| 12 | - linux | |
| 13 | ||
| 14 | # run in container | |
| 15 | sudo: false | |
| 1 | 16 |
| 1 | # Build | |
| 2 | ||
| 3 | This document describes how to build the application. | |
| 4 | ||
| 5 | # Requirements | |
| 6 | ||
| 7 | Download and install the following software packages: | |
| 8 | ||
| 9 | * [OpenJDK 14](https://openjdk.java.net) | |
| 10 | * [Gradle 6.4](https://gradle.org/releases) | |
| 11 | ||
| 12 | # Compile | |
| 13 | ||
| 14 | Build the application as follows: | |
| 15 | ||
| 16 | gradle clean jar | |
| 17 | ||
| 18 | The application is built. | |
| 19 | ||
| 20 | # Run | |
| 21 | ||
| 22 | After the application is compiled, run it as follows: | |
| 23 | ||
| 24 | java -jar build/libs/scrivenvar.jar | |
| 25 | ||
| 26 | On Windows: | |
| 27 | ||
| 28 | java -jar build\libs\scrivenvar.jar | |
| 29 | ||
| 1 | 30 |
| 1 | # Credits | |
| 2 | ||
| 3 | * Dave Jarvis: [Scrivenvar](https://github.com/DaveJarvis/scrivenvar/) | |
| 4 | * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx) | |
| 5 | * Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [ReactFX](https://github.com/TomasMikula/ReactFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX) | |
| 6 | * Mikael Grev: [MigLayout](http://www.miglayout.com/) | |
| 7 | * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java) | |
| 8 | * Vladimir Schneider: [flexmark](https://website.com) | |
| 9 | * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx) | |
| 10 | * Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet) | |
| 11 | * David Croft, [File Preferences](https://github.com/eis/simple-suomi24-java-client/tree/master/src/main/java/net/infotrek/util/prefs) | |
| 12 | * Alex Bertram, [Renjin](https://www.renjin.org/) | |
| 13 | * Michael Kay, [XSLT Processor](http://www.saxonica.com/) | |
| 14 | ||
| 1 | 15 |
| 1 | # License | |
| 2 | ||
| 3 | Copyright 2020 White Magic Software, Ltd. | |
| 4 | All rights reserved. | |
| 5 | ||
| 6 | Redistribution and use in source and binary forms, with or without | |
| 7 | modification, are permitted provided that the following conditions are met: | |
| 8 | ||
| 9 | * Redistributions of source code must retain the above copyright | |
| 10 | notice, this list of conditions and the following disclaimer. | |
| 11 | ||
| 12 | * Redistributions in binary form must reproduce the above copyright | |
| 13 | notice, this list of conditions and the following disclaimer in the | |
| 14 | documentation and/or other materials provided with the distribution. | |
| 15 | ||
| 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 27 |
| 1 | # R Functions | |
| 2 | ||
| 3 | Import the files in this directory into the application, which include: | |
| 4 | ||
| 5 | * pluralize.R | |
| 6 | * possessive.R | |
| 7 | ||
| 8 | # pluralize.R | |
| 9 | ||
| 10 | This file defines a function that implements most of Damian Conway's [An Algorithmic Approach to English Pluralization](http://blob.perl.org/tpc/1998/User_Applications/Algorithmic%20Approach%20Plurals/Algorithmic_Plurals.html). | |
| 11 | ||
| 12 | ## Usage | |
| 13 | ||
| 14 | Example usages of the pluralize function include: | |
| 15 | ||
| 16 | `r#pluralize( 'mouse' )` - mice | |
| 17 | `r#pluralize( 'buzz' )` - buzzes | |
| 18 | `r#pluralize( 'bus' )` - busses | |
| 19 | ||
| 20 | # possessive.R | |
| 21 | ||
| 22 | This file defines a function that applies possessives to English words. | |
| 23 | ||
| 24 | ## Usage | |
| 25 | ||
| 26 | Example usages of the possessive function include: | |
| 27 | ||
| 28 | `r#pos( 'Ross' )` - Ross' | |
| 29 | `r#pos( 'Ruby' )` - Ruby's | |
| 30 | `r#pos( 'Lois' )` - Lois' | |
| 31 | `r#pos( 'my' )` - mine | |
| 32 | `r#pos( 'Your' )` - Yours | |
| 33 | ||
| 1 | 34 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 26 | # http://goo.gl/oRL4MP | |
| 27 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 28 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 29 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 30 | # ----------------------------------------------------------------------------- | |
| 31 | pluralize <- function( s, n ) { | |
| 32 | result <- s | |
| 33 | ||
| 34 | # Partial implementation of Conway's algorithm for nouns. | |
| 35 | if( n != 1 ) { | |
| 36 | if( pl.noninflective( s ) || | |
| 37 | pl.suffix( "es", s ) || | |
| 38 | pl.suffix( "fish", s ) || | |
| 39 | pl.suffix( "ois", s ) || | |
| 40 | pl.suffix( "sheep", s ) || | |
| 41 | pl.suffix( "deer", s ) || | |
| 42 | pl.suffix( "pox", s ) || | |
| 43 | pl.suffix( "[A-Z].*ese", s ) || | |
| 44 | pl.suffix( "itis", s ) ) { | |
| 45 | # 1. Retain non-inflective user-mapped noun as is. | |
| 46 | # 2. Retain non-inflective plural as is. | |
| 47 | result <- s | |
| 48 | } | |
| 49 | else if( pl.is.irregular.pl( s ) ) { | |
| 50 | # 4. Change irregular plurals based on mapping. | |
| 51 | result <- pl.irregular.pl( s ) | |
| 52 | } | |
| 53 | else if( pl.is.irregular.es( s ) ) { | |
| 54 | # x. From Shevek's | |
| 55 | result <- pl.inflect( s, "", "es" ) | |
| 56 | } | |
| 57 | else if( pl.suffix( "man", s ) ) { | |
| 58 | # 5. For -man, change -an to -en | |
| 59 | result <- pl.inflect( s, "an", "en" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 62 | # 5. For [lm]ouse, change -ouse to -ice | |
| 63 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "tooth", s ) ) { | |
| 66 | # 5. For -tooth, change -ooth to -eeth | |
| 67 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "goose", s ) ) { | |
| 70 | # 5. For -goose, change -oose to -eese | |
| 71 | result <- pl.inflect( s, "oose", "eese" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "foot", s ) ) { | |
| 74 | # 5. For -foot, change -oot to -eet | |
| 75 | result <- pl.inflect( s, "oot", "eet" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "zoon", s ) ) { | |
| 78 | # 5. For -zoon, change -on to -a | |
| 79 | result <- pl.inflect( s, "on", "a" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 82 | # 5. Change -cis, -sis, -xis to -es | |
| 83 | result <- pl.inflect( s, "is", "es" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "([cs]h|ss|zz|x|s)", s ) ) { | |
| 86 | # 8. Change -ch, -sh, -ss, -zz, -x, -s to -es | |
| 87 | result <- pl.inflect( s, "", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 90 | # 9. Change -f to -ves | |
| 91 | result <- pl.inflect( s, "f", "ves" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 94 | # 10. Change -fe to -ves | |
| 95 | result <- pl.inflect( s, "fe", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[aeiou]y", s ) ) { | |
| 98 | # 11. Change -[aeiou]y to -ys | |
| 99 | result <- pl.inflect( s, "", "s" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "y", s ) ) { | |
| 102 | # 12. Change -y to -ies | |
| 103 | result <- pl.inflect( s, "y", "ies" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "z", s ) ) { | |
| 106 | # x. Change -z to -zzes | |
| 107 | result <- pl.inflect( s, "", "zes" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # ----------------------------------------------------------------------------- | |
| 119 | # Returns the given string (s) with its suffix replaced by r. | |
| 120 | # ----------------------------------------------------------------------------- | |
| 121 | pl.inflect <- function( s, suffix, r ) { | |
| 122 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 123 | } | |
| 124 | ||
| 125 | # ----------------------------------------------------------------------------- | |
| 126 | # Answers whether the given string (s) has the given ending. | |
| 127 | # ----------------------------------------------------------------------------- | |
| 128 | pl.suffix <- function( ending, s ) { | |
| 129 | grepl( paste( ending, "$", sep="" ), s ) | |
| 130 | } | |
| 131 | ||
| 132 | # ----------------------------------------------------------------------------- | |
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | pl.noninflective <- function( s ) { | |
| 136 | v <- c( | |
| 137 | "aircraft", | |
| 138 | "Bhutanese", | |
| 139 | "bison", | |
| 140 | "bream", | |
| 141 | "Burmese", | |
| 142 | "carp", | |
| 143 | "chassis", | |
| 144 | "Chinese", | |
| 145 | "clippers", | |
| 146 | "cod", | |
| 147 | "contretemps", | |
| 148 | "corps", | |
| 149 | "debris", | |
| 150 | "djinn", | |
| 151 | "eland", | |
| 152 | "elk", | |
| 153 | "flounder", | |
| 154 | "fracas", | |
| 155 | "gallows", | |
| 156 | "graffiti", | |
| 157 | "headquarters", | |
| 158 | "high-jinks", | |
| 159 | "homework", | |
| 160 | "hovercraft", | |
| 161 | "innings", | |
| 162 | "Japanese", | |
| 163 | "Lebanese", | |
| 164 | "mackerel", | |
| 165 | "means", | |
| 166 | "mews", | |
| 167 | "mice", | |
| 168 | "mumps", | |
| 169 | "news", | |
| 170 | "pincers", | |
| 171 | "pliers", | |
| 172 | "Portuguese", | |
| 173 | "proceedings", | |
| 174 | "salmon", | |
| 175 | "scissors", | |
| 176 | "sea-bass", | |
| 177 | "Senegalese", | |
| 178 | "shears", | |
| 179 | "Siamese", | |
| 180 | "Sinhalese", | |
| 181 | "spacecraft", | |
| 182 | "swine", | |
| 183 | "trout", | |
| 184 | "tuna", | |
| 185 | "Vietnamese", | |
| 186 | "watercraft", | |
| 187 | "whiting", | |
| 188 | "wildebeest" | |
| 189 | ) | |
| 190 | ||
| 191 | is.element( s, v ) | |
| 192 | } | |
| 193 | ||
| 194 | # ----------------------------------------------------------------------------- | |
| 195 | # Answers whether the given string (s) is an irregular plural. | |
| 196 | # ----------------------------------------------------------------------------- | |
| 197 | pl.is.irregular.pl <- function( s ) { | |
| 198 | # Could be refactored with pl.irregular.pl... | |
| 199 | v <- c( | |
| 200 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 201 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 202 | ) | |
| 203 | ||
| 204 | is.element( s, v ) | |
| 205 | } | |
| 206 | ||
| 207 | # ----------------------------------------------------------------------------- | |
| 208 | # Call to pluralize an irregular noun. Only call after confirming | |
| 209 | # the noun is irregular via pl.is.irregular.pl. | |
| 210 | # ----------------------------------------------------------------------------- | |
| 211 | pl.irregular.pl <- function( s ) { | |
| 212 | v <- list( | |
| 213 | "beef" = "beefs", | |
| 214 | "brother" = "brothers", | |
| 215 | "child" = "children", | |
| 216 | "cow" = "cows", | |
| 217 | "ephemeris" = "ephemerides", | |
| 218 | "genie" = "genies", | |
| 219 | "money" = "moneys", | |
| 220 | "mongoose" = "mongooses", | |
| 221 | "mythos" = "mythoi", | |
| 222 | "octopus" = "octopuses", | |
| 223 | "ox" = "oxen", | |
| 224 | "soliloquy" = "soliloquies", | |
| 225 | "trilby" = "trilbys" | |
| 226 | ) | |
| 227 | ||
| 228 | # Faster version of v[[ s ]] | |
| 229 | .subset2( v, s ) | |
| 230 | } | |
| 231 | ||
| 232 | # ----------------------------------------------------------------------------- | |
| 233 | # Answers whether the given string (s) pluralizes with -es. | |
| 234 | # ----------------------------------------------------------------------------- | |
| 235 | pl.is.irregular.es <- function( s ) { | |
| 236 | v <- c( | |
| 237 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 238 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 239 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 240 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 241 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 242 | ) | |
| 243 | ||
| 244 | is.element( s, v ) | |
| 245 | } | |
| 246 | ||
| 1 | 247 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Returns leftmost n characters of s. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | lstr <- function( s, n = 1 ) { | |
| 28 | substr( s, 0, n ) | |
| 29 | } | |
| 30 | ||
| 31 | # ----------------------------------------------------------------------------- | |
| 32 | # Returns rightmost n characters of s. | |
| 33 | # ----------------------------------------------------------------------------- | |
| 34 | rstr <- function( s, n = 1 ) { | |
| 35 | l <- nchar( s ) | |
| 36 | substr( s, l - n + 1, l ) | |
| 37 | } | |
| 38 | ||
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | # Returns the possessive form of the given word, s. | |
| 41 | # ----------------------------------------------------------------------------- | |
| 42 | pos <- function( s ) { | |
| 43 | lcs <- tolower( s ) | |
| 44 | pronouns <- c( 'your', 'our', 'her', 'it', 'their' ) | |
| 45 | ||
| 46 | if( lcs == 'my' ) { | |
| 47 | # Change "[Mm]y" to "[Mm]ine". | |
| 48 | s <- paste0( lstr( s, 1 ), "ine" ) | |
| 49 | } | |
| 50 | else if( lcs %in% pronouns ) { | |
| 51 | # Append an s to most pronouns. | |
| 52 | s <- paste0( s, 's' ) | |
| 53 | } | |
| 54 | else if( lcs != 'his' ) { | |
| 55 | # Possessive for all other words except 'his'. | |
| 56 | s <- paste0( s, ifelse( rstr( s, 1 ) == 's', "'" ,"'s" ) ) | |
| 57 | } | |
| 58 | ||
| 59 | s | |
| 60 | } | |
| 61 | ||
| 1 | 62 |
| 1 |  | |
| 2 | ||
| 3 | # $application.title$ | |
| 4 | ||
| 5 | A text editor that uses [interpolated strings](https://en.wikipedia.org/wiki/String_interpolation) to reference externally defined values. | |
| 6 | ||
| 7 | ## Requirements | |
| 8 | ||
| 9 | Download and install the following software packages: | |
| 10 | ||
| 11 | * [OpenJDK 14](https://openjdk.java.net) | |
| 12 | ||
| 13 | ## Quick Start | |
| 14 | ||
| 15 | Complete the following steps to run the application: | |
| 16 | ||
| 17 | 1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) | |
| 18 | `scrivenvar.jar`. | |
| 19 | 1. Double-click `scrivenvar.jar` to start the application. | |
| 20 | ||
| 21 | ## Command Line Start | |
| 22 | ||
| 23 | If the quick start fails, run the application as follows: | |
| 24 | ||
| 25 | 1. Open a command prompt. | |
| 26 | 1. Change to the download directory containing the archive file. | |
| 27 | 1. Run: `java -jar scrivenvar.jar` | |
| 28 | ||
| 29 | ## Features | |
| 30 | ||
| 31 | * R integration | |
| 32 | * User-defined variables, interpolated | |
| 33 | * Real-time preview with variable substitution | |
| 34 | * Auto-complete variable names based on variable values | |
| 35 | * XML document transformation using XSLT3 or older | |
| 36 | * Platform independent (Windows, Linux, MacOS) | |
| 37 | ||
| 38 | ## Usage | |
| 39 | ||
| 40 | See the following documents for more information: | |
| 41 | ||
| 42 | * [USAGE.md](USAGE.md) - how variable definitions and string interpolation work. | |
| 43 | * [USAGE-R.md](USAGE-R.md) - how to call R functions in R Markdown documents. | |
| 44 | ||
| 45 | ## Future Features | |
| 46 | ||
| 47 | * Spell check | |
| 48 | * Search and replace using variables | |
| 49 | * Re-organize variable names | |
| 50 | ||
| 51 | ## Screenshot | |
| 52 | ||
| 53 |  | |
| 54 | ||
| 55 | ## License | |
| 56 | ||
| 57 | This software is licensed under the [BSD 2-Clause License](LICENSE.md). | |
| 58 | ||
| 1 | 59 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the [R](https://www.r-project.org/) | |
| 4 | programming language from within the application. The application uses an | |
| 5 | interpreter known as [Renjin](https://www.renjin.org/) to integrate with R. | |
| 6 | ||
| 7 | # Hello World | |
| 8 | ||
| 9 | Complete the following steps to see R in action: | |
| 10 | ||
| 11 | 1. Start the application. | |
| 12 | 1. Click **File → New** to create a new file. | |
| 13 | 1. Click **File → Save As**. | |
| 14 | 1. Set **Name** to: `addition.Rmd` | |
| 15 | 1. Click **Save**. | |
| 16 | ||
| 17 | Setting the file name extension tells the application what processor to | |
| 18 | use when transforming the contents for display in the preview pane. Continue | |
| 19 | by typing in the following text, including the backticks: | |
| 20 | ||
| 21 | ```r | |
| 22 | `r#1 + 1` | |
| 23 | ``` | |
| 24 | ||
| 25 | The preview pane shows the result of `1` plus `1`: | |
| 26 | ||
| 27 | ``` | |
| 28 | 2.0 | |
| 29 | ``` | |
| 30 | ||
| 31 | # Bootstrap Script | |
| 32 | ||
| 33 | Being able to run R code while editing an R Markdown document is convenient. | |
| 34 | Having the ability to call functions is where the power of R can be | |
| 35 | leveraged. | |
| 36 | ||
| 37 | Complete the following steps to call an R function from your own library: | |
| 38 | ||
| 39 | 1. Click **File → New** to create a new file. | |
| 40 | 1. Click **File → Save As**. | |
| 41 | 1. Browse to your home directory. | |
| 42 | 1. Set **Name** to: `library.R`. | |
| 43 | 1. Click **Save**. | |
| 44 | 1. Set the contents to: | |
| 45 | ``` r | |
| 46 | sum <- function( a, b ) { | |
| 47 | a + b | |
| 48 | } | |
| 49 | ``` | |
| 50 | 1. Click the **Save** icon. | |
| 51 | 1. Click **R → Script**. | |
| 52 | 1. Set the **R Startup Script** contents to: | |
| 53 | ``` r | |
| 54 | source( 'library.R' ); | |
| 55 | ``` | |
| 56 | 1. Click **OK**. | |
| 57 | 1. Create a new file. | |
| 58 | 1. Set the contents to: | |
| 59 | ``` r | |
| 60 | `r#sum( 5, 5 )` | |
| 61 | ``` | |
| 62 | 1. Save the file as `sum.R`. | |
| 63 | ||
| 64 | The preview panel shows the result of calling the `sum` function: | |
| 65 | ||
| 66 | ``` | |
| 67 | 10.0 | |
| 68 | ``` | |
| 69 | ||
| 70 | This shows how the bootstrap script can load `library.R`, which defines | |
| 71 | a `sum` function that is called by name in the Markdown document. | |
| 72 | ||
| 73 | # Working Directory | |
| 74 | ||
| 75 | R files may be sourced from any directory, not just the user's home | |
| 76 | directory. Accomplish this as follows: | |
| 77 | ||
| 78 | 1. Click **R → Directory**. | |
| 79 | 1. Set **Directory** to a different directory. | |
| 80 | 1. Click **OK**. | |
| 81 | 1. Create the directory if it does not exist. | |
| 82 | 1. Move `library.R` into the directory. | |
| 83 | 1. Append a new function to `library.R` as follows: | |
| 84 | ``` r | |
| 85 | mul <- function( a, b ) { | |
| 86 | a * b | |
| 87 | } | |
| 88 | ``` | |
| 89 | 1. Click **R → Script**. | |
| 90 | 1. Set the **R Startup Script** contents to: | |
| 91 | ``` r | |
| 92 | setwd( '$application.r.working.directory$' ); | |
| 93 | source( 'library.R' ); | |
| 94 | ``` | |
| 95 | 1. Change `sum.Rmd` to: | |
| 96 | ``` r | |
| 97 | `r#mul( 5, 5 )` | |
| 98 | ``` | |
| 99 | 1. Close the file `sum.Rmd`. | |
| 100 | 1. Confirm saving the file when prompted. | |
| 101 | 1. Re-open `sum.Rmd`. | |
| 102 | ||
| 103 | The preview panel shows: | |
| 104 | ||
| 105 | ``` | |
| 106 | 25.0 | |
| 107 | ``` | |
| 108 | ||
| 109 | Calling `setwd` using `'$application.r.working.directory$'` changes the | |
| 110 | working directory where the R engine searches for source files. | |
| 111 | ||
| 112 | # YAML Definitions | |
| 113 | ||
| 114 | To see how variable definitions work in R, try the following: | |
| 115 | ||
| 116 | 1. Create a new file. | |
| 117 | 1. Change the contents to (use spaces not tabs): | |
| 118 | ``` yaml | |
| 119 | project: | |
| 120 | title: Project Title | |
| 121 | author: Author Name | |
| 122 | ``` | |
| 123 | 1. Save the file as `definitions.yaml`. | |
| 124 | 1. Click **File → Open**. | |
| 125 | 1. Set **Source Files** to **Definition Files**. | |
| 126 | 1. Select `definitions.yaml`. | |
| 127 | 1. Click **Open**. | |
| 128 | 1. Open `sum.Rmd` if it is not already open. | |
| 129 | 1. Type: `je` | |
| 130 | 1. Press `Ctrl+Space` | |
| 131 | ||
| 132 | The editor inserts the following text (matches `je` against Pro**je**ct): | |
| 133 | ||
| 134 | ``` r | |
| 135 | `r#x( v$project$title )` | |
| 136 | ``` | |
| 137 | ||
| 138 | The preview panel shows: | |
| 139 | ||
| 140 | ``` | |
| 141 | r#x( 'Project Title' ) | |
| 142 | ``` | |
| 143 | ||
| 144 | This is because the application inserts definition reference names based | |
| 145 | on the type of file being edited. By default, the R engine does not have | |
| 146 | a function named `x` defined. | |
| 147 | ||
| 148 | Continue as follows: | |
| 149 | ||
| 150 | 1. Click **R → Script**. | |
| 151 | 1. Append the following: | |
| 152 | ``` r | |
| 153 | x <- function( s ) { | |
| 154 | tryCatch( { | |
| 155 | r = eval( parse( text = s ) ) | |
| 156 | ||
| 157 | ifelse( is.atomic( r ), r, s ); | |
| 158 | }, | |
| 159 | warning = function( w ) { s }, | |
| 160 | error = function( e ) { s } ) | |
| 161 | } | |
| 162 | ``` | |
| 163 | 1. Click **OK**. | |
| 164 | 1. Close and re-open `sum.Rmd`. | |
| 165 | ||
| 166 | The preview panel shows: | |
| 167 | ||
| 168 | ``` | |
| 169 | 25.0 | |
| 170 | ||
| 171 | Project Title | |
| 172 | ``` | |
| 173 | ||
| 174 | The `x` function attempts to evaluate the expression defined by the YAML | |
| 175 | variable. This means that the YAML definitions can also include expressions | |
| 176 | that R is capable of evaluating. | |
| 177 | ||
| 178 | While the `x` function can be defined within the R Startup Script, it is | |
| 179 | better practice to put it into its own library so that it can be reused | |
| 180 | outside of the application. | |
| 181 | ||
| 1 | 182 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the application. | |
| 4 | ||
| 5 | # Variable Definitions | |
| 6 | ||
| 7 | Variable definitions provide a way to insert key names having associated values into a document. The variable names and values are declared inside an external file using the [YAML](http://www.yaml.org/) file format. Simply put, variables are written in the file as follows: | |
| 8 | ||
| 9 | ``` | |
| 10 | key: value | |
| 11 | ``` | |
| 12 | ||
| 13 | Any number of variables can be defined, in any order: | |
| 14 | ||
| 15 | ``` | |
| 16 | key_1: Value 1 | |
| 17 | key_2: Value 2 | |
| 18 | ``` | |
| 19 | ||
| 20 | Variables can reference other variables by enclosing the key name within dollar symbols: | |
| 21 | ||
| 22 | ``` | |
| 23 | key: Value | |
| 24 | key_1: $key$ 1 | |
| 25 | key_2: $key$ 2 | |
| 26 | ``` | |
| 27 | ||
| 28 | Variables can use a nested structure to help group related information: | |
| 29 | ||
| 30 | ``` | |
| 31 | novel: | |
| 32 | title: Book Title | |
| 33 | author: Author Name | |
| 34 | isbn: 978-3-16-148410-0 | |
| 35 | ``` | |
| 36 | ||
| 37 | Use a period to reference nested keys, such as: | |
| 38 | ||
| 39 | ``` | |
| 40 | novel: | |
| 41 | author: Author Name | |
| 42 | copyright: | |
| 43 | owner: $novel.author$ | |
| 44 | ``` | |
| 45 | ||
| 46 | Save the variable definitions in a file having an extension of `.yaml` or `.yml`. | |
| 47 | ||
| 48 | # Document Editing | |
| 49 | ||
| 50 | The application's purpose is to completely separate the document's content from its presentation. To achieve this, documents are composed using a [plain text](http://spec.commonmark.org/0.28/) format. | |
| 51 | ||
| 52 | ## Create Document | |
| 53 | ||
| 54 | Start a new document as follows: | |
| 55 | ||
| 56 | 1. Start the application. | |
| 57 | 1. Click **File → New** to create an empty document to edit. | |
| 58 | 1. Click **File → Open** to open a variable definition file. | |
| 59 | 1. Change **Source Files** to **Definition Files** to list definition files. | |
| 60 | 1. Browse to and select a file saved with a `.yaml` or `.yml` extension. | |
| 61 | 1. Click **Open**. | |
| 62 | ||
| 63 | The variable definitions appear in the variable definition pane under the heading of **Definitions**. | |
| 64 | ||
| 65 | ## Edit Document | |
| 66 | ||
| 67 | Edit the document as normal. Notice how the preview pane updates as new content is added. The toolbar shows various icons that perform different formatting operations. Try them to see how they appear in the preview pane. Other operations not shown on the toolbar include: | |
| 68 | ||
| 69 | * Struck text (enclose the words within `~~` and `~~`) | |
| 70 | * Horizontal rule (use `---` on an otherwise empty line). | |
| 71 | ||
| 72 | The preview pane shows one way to interpret and format the document, but many other presentations are possible. | |
| 73 | ||
| 74 | ## Insert Variable | |
| 75 | ||
| 76 | Let's assume that the variable definitions loaded into the application include: | |
| 77 | ||
| 78 | ``` | |
| 79 | novel: | |
| 80 | title: Diary of $novel.author$ | |
| 81 | author: Anne Frank | |
| 82 | ``` | |
| 83 | ||
| 84 | To reference a variable, type in the key name enclosed within dollar symbols, such as: | |
| 85 | ||
| 86 | ``` | |
| 87 | The novel "$novel.title$" is one of the most widely read books in the world. | |
| 88 | ``` | |
| 89 | ||
| 90 | The preview pane shows: | |
| 91 | ||
| 92 | > The novel "Diary of Anne Frank" is one of the most widely read books in the world. | |
| 93 | ||
| 94 | As it is laborious to type in variable names, it is possible to inject the variable name using autocomplete. Accomplish this as follows: | |
| 95 | ||
| 96 | 1. Create a new file. | |
| 97 | 1. Type in a partial variable value, such as **Dia**. | |
| 98 | 1. Press `Ctrl+Space` (hold down the `Control` key and tap the spacebar). | |
| 99 | ||
| 100 | The editor shows: | |
| 101 | ||
| 102 | ``` | |
| 103 | $novel.title$ | |
| 104 | ``` | |
| 105 | ||
| 106 | The preview pane shows: | |
| 107 | ||
| 108 | ``` | |
| 109 | Diary of Anne Frank | |
| 110 | ``` | |
| 111 | ||
| 112 | The variable name is inserted into the document and the preview pane shows the variable's value. | |
| 113 | ||
| 1 | 114 |
| 1 | --- | |
| 2 | application: | |
| 3 | title: "Scrivenvar" | |
| 1 | 4 |
| 1 | plugins { | |
| 2 | id 'application' | |
| 3 | id 'org.openjfx.javafxplugin' version '0.0.8' | |
| 4 | id 'com.palantir.git-version' version '0.12.3' | |
| 5 | } | |
| 6 | ||
| 7 | repositories { | |
| 8 | mavenCentral() | |
| 9 | jcenter() | |
| 10 | ||
| 11 | maven { | |
| 12 | url 'https://oss.sonatype.org/content/repositories/snapshots/' | |
| 13 | } | |
| 14 | ||
| 15 | maven { | |
| 16 | url "https://nexus.bedatadriven.com/content/groups/public" | |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | dependencies { | |
| 21 | implementation 'org.controlsfx:controlsfx:11.0.1' | |
| 22 | implementation 'org.fxmisc.richtext:richtextfx:0.10.5' | |
| 23 | implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3' | |
| 24 | implementation 'com.miglayout:miglayout-javafx:5.2' | |
| 25 | implementation 'com.dlsc.preferencesfx:preferencesfx-core:11.6.0' | |
| 26 | implementation 'com.vladsch.flexmark:flexmark:0.62.2' | |
| 27 | implementation 'com.vladsch.flexmark:flexmark-ext-tables:0.62.2' | |
| 28 | implementation 'com.vladsch.flexmark:flexmark-ext-superscript:0.62.2' | |
| 29 | implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' | |
| 30 | implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0' | |
| 31 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' | |
| 32 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.0' | |
| 33 | implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.0' | |
| 34 | implementation 'org.ahocorasick:ahocorasick:0.4.0' | |
| 35 | implementation 'org.yaml:snakeyaml:1.26' | |
| 36 | implementation 'com.ximpleware:vtd-xml:2.13.4' | |
| 37 | implementation 'net.sf.saxon:Saxon-HE:10.1' | |
| 38 | implementation 'org.apache.commons:commons-configuration2:2.7' | |
| 39 | implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' | |
| 40 | implementation 'de.jensd:fontawesomefx-commons:11.0' | |
| 41 | implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-11' | |
| 42 | implementation 'org.renjin:renjin-script-engine:3.5-beta76' | |
| 43 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20' | |
| 44 | implementation 'org.jsoup:jsoup:1.13.1' | |
| 45 | implementation 'org.apache.xmlgraphics:batik-all:1.13' | |
| 46 | ||
| 47 | def os = ['win', 'linux', 'mac'] | |
| 48 | def fx = ['controls', 'graphics', 'fxml', 'swing'] | |
| 49 | ||
| 50 | fx.each { fxitem -> | |
| 51 | os.each { ositem -> | |
| 52 | runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}" | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | testImplementation('org.junit.jupiter:junit-jupiter-api:5.4.2') | |
| 57 | testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2') | |
| 58 | } | |
| 59 | ||
| 60 | javafx { | |
| 61 | version = "14" | |
| 62 | modules = ['javafx.controls', 'javafx.graphics', 'javafx.swing'] | |
| 63 | } | |
| 64 | ||
| 65 | compileJava { | |
| 66 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | |
| 67 | } | |
| 68 | ||
| 69 | sourceCompatibility = JavaVersion.VERSION_11 | |
| 70 | applicationName = 'scrivenvar' | |
| 71 | version = gitVersion() | |
| 72 | mainClassName = "com.${applicationName}.Main" | |
| 73 | def launcherClassName = "com.${applicationName}.Launcher" | |
| 74 | ||
| 75 | def propertiesFile = new File("src/main/resources/com/${applicationName}/app.properties") | |
| 76 | propertiesFile.write("application.version=${version}") | |
| 77 | ||
| 78 | jar { | |
| 79 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE | |
| 80 | ||
| 81 | manifest { | |
| 82 | attributes 'Main-Class': launcherClassName | |
| 83 | } | |
| 84 | ||
| 85 | from { | |
| 86 | (configurations.runtimeClasspath.findAll { !it.path.endsWith(".pom") }).collect { | |
| 87 | it.isDirectory() ? it : zipTree(it) | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | archiveFileName = "${applicationName}.jar" | |
| 92 | ||
| 93 | exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' | |
| 94 | } | |
| 95 | ||
| 96 | distributions { | |
| 97 | main { | |
| 98 | distributionBaseName = applicationName | |
| 99 | contents { | |
| 100 | from { ['LICENSE.md', 'README.md'] } | |
| 101 | into('images') { | |
| 102 | from { 'images' } | |
| 103 | } | |
| 104 | } | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | test { | |
| 109 | useJUnitPlatform() | |
| 110 | } | |
| 1 | 111 |
| 1 | org.gradle.jvmargs=-Xmx1G -XX:MaxPermSize=512m | |
| 2 | ||
| 1 | 3 |
| 1 | Copyright (c) 2015 Karl Tauber <karl@jformdesigner.com> | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright | |
| 8 | notice, this list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright | |
| 11 | notice, this list of conditions and the following disclaimer in the | |
| 12 | documentation and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 25 |
| 1 | 1 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.predicates.files.FileTypePredicate; | |
| 31 | import com.scrivenvar.service.Settings; | |
| 32 | ||
| 33 | import java.nio.file.Path; | |
| 34 | import java.util.Iterator; | |
| 35 | import java.util.List; | |
| 36 | ||
| 37 | import static com.scrivenvar.Constants.GLOB_PREFIX_FILE; | |
| 38 | import static java.lang.String.format; | |
| 39 | ||
| 40 | /** | |
| 41 | * Provides common behaviours for factories that instantiate classes based on | |
| 42 | * file type. | |
| 43 | * | |
| 44 | * @author White Magic Software, Ltd. | |
| 45 | */ | |
| 46 | public class AbstractFileFactory { | |
| 47 | ||
| 48 | private static final String MSG_UNKNOWN_FILE_TYPE = | |
| 49 | "Unknown type '%s' for file '%s'."; | |
| 50 | ||
| 51 | private final Settings mSettings = Services.load( Settings.class ); | |
| 52 | ||
| 53 | /** | |
| 54 | * Determines the file type from the path extension. This should only be | |
| 55 | * called when it is known that the file type won't be a definition file | |
| 56 | * (e.g., YAML or other definition source), but rather an editable file | |
| 57 | * (e.g., Markdown, XML, etc.). | |
| 58 | * | |
| 59 | * @param path The path with a file name extension. | |
| 60 | * @return The FileType for the given path. | |
| 61 | */ | |
| 62 | public FileType lookup( final Path path ) { | |
| 63 | return lookup( path, GLOB_PREFIX_FILE ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Creates a file type that corresponds to the given path. | |
| 68 | * | |
| 69 | * @param path Reference to a variable definition file. | |
| 70 | * @param prefix One of GLOB_PREFIX_DEFINITION or GLOB_PREFIX_FILE. | |
| 71 | * @return The file type that corresponds to the given path. | |
| 72 | */ | |
| 73 | protected FileType lookup( final Path path, final String prefix ) { | |
| 74 | assert path != null; | |
| 75 | assert prefix != null; | |
| 76 | ||
| 77 | final Settings properties = getSettings(); | |
| 78 | final Iterator<String> keys = properties.getKeys( prefix ); | |
| 79 | ||
| 80 | boolean found = false; | |
| 81 | FileType fileType = null; | |
| 82 | ||
| 83 | while( keys.hasNext() && !found ) { | |
| 84 | final String key = keys.next(); | |
| 85 | final List<String> patterns = properties.getStringSettingList( key ); | |
| 86 | final FileTypePredicate predicate = new FileTypePredicate( patterns ); | |
| 87 | ||
| 88 | if( found = predicate.test( path.toFile() ) ) { | |
| 89 | // Remove the EXTENSIONS_PREFIX to get the filename extension mapped | |
| 90 | // to a standard name (as defined in the settings.properties file). | |
| 91 | final String suffix = key.replace( prefix + ".", "" ); | |
| 92 | fileType = FileType.from( suffix ); | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | return fileType; | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * Throws IllegalArgumentException because the given path could not be | |
| 101 | * recognized. This exists because | |
| 102 | * | |
| 103 | * @param type The detected path type (protocol, file extension, etc.). | |
| 104 | * @param path The path to a source of definitions. | |
| 105 | */ | |
| 106 | protected void unknownFileType( final String type, final String path ) { | |
| 107 | final String msg = format( MSG_UNKNOWN_FILE_TYPE, type, path ); | |
| 108 | throw new IllegalArgumentException( msg ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Return the singleton Settings instance. | |
| 113 | * | |
| 114 | * @return A non-null instance. | |
| 115 | */ | |
| 116 | private Settings getSettings() { | |
| 117 | return this.mSettings; | |
| 118 | } | |
| 119 | } | |
| 1 | 120 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 31 | ||
| 32 | /** | |
| 33 | * Hides dependency on {@link MigPane} from subclasses. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public abstract class AbstractPane extends MigPane { | |
| 38 | } | |
| 1 | 39 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Settings; | |
| 31 | ||
| 32 | /** | |
| 33 | * Defines application-wide default values. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class Constants { | |
| 38 | ||
| 39 | private static final Settings SETTINGS = Services.load( Settings.class ); | |
| 40 | ||
| 41 | /** | |
| 42 | * Prevent instantiation. | |
| 43 | */ | |
| 44 | private Constants() { | |
| 45 | } | |
| 46 | ||
| 47 | private static String get( final String key ) { | |
| 48 | return SETTINGS.getSetting( key, "" ); | |
| 49 | } | |
| 50 | ||
| 51 | @SuppressWarnings("SameParameterValue") | |
| 52 | private static int get( final String key, final int defaultValue ) { | |
| 53 | return SETTINGS.getSetting( key, defaultValue ); | |
| 54 | } | |
| 55 | ||
| 56 | // Bootstrapping... | |
| 57 | public static final String SETTINGS_NAME = | |
| 58 | "/com/scrivenvar/settings.properties"; | |
| 59 | ||
| 60 | public static final String APP_TITLE = get( "application.title" ); | |
| 61 | public static final String APP_BUNDLE_NAME = get( "application.messages" ); | |
| 62 | ||
| 63 | // Prevent double events when updating files on Linux (save and timestamp). | |
| 64 | public static final int APP_WATCHDOG_TIMEOUT = get( | |
| 65 | "application.watchdog.timeout", 200 ); | |
| 66 | ||
| 67 | public static final String STYLESHEET_SCENE = get( "file.stylesheet.scene" ); | |
| 68 | public static final String STYLESHEET_MARKDOWN = get( | |
| 69 | "file.stylesheet.markdown" ); | |
| 70 | public static final String STYLESHEET_PREVIEW = get( | |
| 71 | "file.stylesheet.preview" ); | |
| 72 | ||
| 73 | public static final String FILE_LOGO_16 = get( "file.logo.16" ); | |
| 74 | public static final String FILE_LOGO_32 = get( "file.logo.32" ); | |
| 75 | public static final String FILE_LOGO_128 = get( "file.logo.128" ); | |
| 76 | public static final String FILE_LOGO_256 = get( "file.logo.256" ); | |
| 77 | public static final String FILE_LOGO_512 = get( "file.logo.512" ); | |
| 78 | ||
| 79 | public static final String PREFS_ROOT = get( "preferences.root" ); | |
| 80 | public static final String PREFS_STATE = get( "preferences.root.state" ); | |
| 81 | ||
| 82 | // Refer to filename extension settings in the configuration file. Do not | |
| 83 | // terminate these prefixes with a period. | |
| 84 | public static final String GLOB_PREFIX_FILE = "file.ext"; | |
| 85 | public static final String GLOB_PREFIX_DEFINITION = | |
| 86 | "definition." + GLOB_PREFIX_FILE; | |
| 87 | ||
| 88 | // Different definition source protocols. | |
| 89 | public static final String DEFINITION_PROTOCOL_UNKNOWN = "unknown"; | |
| 90 | public static final String DEFINITION_PROTOCOL_FILE = "file"; | |
| 91 | ||
| 92 | // Takes two parameters: line number and column number. | |
| 93 | public static final String STATUS_BAR_LINE = "Main.statusbar.line"; | |
| 94 | ||
| 95 | // "OK" text | |
| 96 | public static final String STATUS_BAR_OK = "Main.statusbar.state.default"; | |
| 97 | public static final String STATUS_PARSE_ERROR = "Main.statusbar.parse.error"; | |
| 98 | ||
| 99 | /** | |
| 100 | * Used when creating flat maps relating to resolved variables. | |
| 101 | */ | |
| 102 | public static final int DEFAULT_MAP_SIZE = 64; | |
| 103 | ||
| 104 | public static final String PERSIST_IMAGES_DEFAULT = | |
| 105 | get( "file.stylesheet.scene" ); | |
| 106 | ||
| 107 | /** | |
| 108 | * Default working directory to use for R startup script. | |
| 109 | */ | |
| 110 | public static final String USER_DIRECTORY = System.getProperty( "user.dir" ); | |
| 111 | } | |
| 1 | 112 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * Redistribution and use in source and binary forms, with or without | |
| 5 | * modification, are permitted provided that the following conditions are met: | |
| 6 | * | |
| 7 | * o Redistributions of source code must retain the above copyright | |
| 8 | * notice, this list of conditions and the following disclaimer. | |
| 9 | * | |
| 10 | * o Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * | |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 18 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 19 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 | */ | |
| 26 | package com.scrivenvar; | |
| 27 | ||
| 28 | import com.scrivenvar.editors.EditorPane; | |
| 29 | import com.scrivenvar.editors.markdown.MarkdownEditorPane; | |
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | import javafx.application.Platform; | |
| 33 | import javafx.beans.binding.Bindings; | |
| 34 | import javafx.beans.property.BooleanProperty; | |
| 35 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 36 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 37 | import javafx.beans.property.SimpleBooleanProperty; | |
| 38 | import javafx.beans.value.ChangeListener; | |
| 39 | import javafx.beans.value.ObservableValue; | |
| 40 | import javafx.event.Event; | |
| 41 | import javafx.event.EventHandler; | |
| 42 | import javafx.event.EventType; | |
| 43 | import javafx.scene.Node; | |
| 44 | import javafx.scene.Scene; | |
| 45 | import javafx.scene.control.Tab; | |
| 46 | import javafx.scene.control.Tooltip; | |
| 47 | import javafx.scene.text.Text; | |
| 48 | import javafx.stage.Window; | |
| 49 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 50 | import org.fxmisc.richtext.model.TwoDimensional.Position; | |
| 51 | import org.fxmisc.undo.UndoManager; | |
| 52 | import org.mozilla.universalchardet.UniversalDetector; | |
| 53 | ||
| 54 | import java.io.File; | |
| 55 | import java.nio.charset.Charset; | |
| 56 | import java.nio.file.Files; | |
| 57 | import java.nio.file.Path; | |
| 58 | ||
| 59 | import static com.scrivenvar.Messages.get; | |
| 60 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 61 | import static java.util.Locale.ENGLISH; | |
| 62 | import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward; | |
| 63 | ||
| 64 | /** | |
| 65 | * Editor for a single file. | |
| 66 | * | |
| 67 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 68 | */ | |
| 69 | public final class FileEditorTab extends Tab { | |
| 70 | ||
| 71 | private final Notifier mNotifier = Services.load( Notifier.class ); | |
| 72 | private final EditorPane mEditorPane = new MarkdownEditorPane(); | |
| 73 | ||
| 74 | private final ReadOnlyBooleanWrapper mModified = new ReadOnlyBooleanWrapper(); | |
| 75 | private final BooleanProperty canUndo = new SimpleBooleanProperty(); | |
| 76 | private final BooleanProperty canRedo = new SimpleBooleanProperty(); | |
| 77 | ||
| 78 | /** | |
| 79 | * Character encoding used by the file (or default encoding if none found). | |
| 80 | */ | |
| 81 | private Charset mEncoding = UTF_8; | |
| 82 | ||
| 83 | /** | |
| 84 | * File to load into the editor. | |
| 85 | */ | |
| 86 | private Path mPath; | |
| 87 | ||
| 88 | public FileEditorTab( final Path path ) { | |
| 89 | setPath( path ); | |
| 90 | ||
| 91 | mModified.addListener( ( observable, oldPath, newPath ) -> updateTab() ); | |
| 92 | ||
| 93 | setOnSelectionChanged( e -> { | |
| 94 | if( isSelected() ) { | |
| 95 | Platform.runLater( this::activated ); | |
| 96 | } | |
| 97 | } ); | |
| 98 | } | |
| 99 | ||
| 100 | private void updateTab() { | |
| 101 | setText( getTabTitle() ); | |
| 102 | setGraphic( getModifiedMark() ); | |
| 103 | setTooltip( getTabTooltip() ); | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Returns the base filename (without the directory names). | |
| 108 | * | |
| 109 | * @return The untitled text if the path hasn't been set. | |
| 110 | */ | |
| 111 | private String getTabTitle() { | |
| 112 | return getPath().getFileName().toString(); | |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Returns the full filename represented by the path. | |
| 117 | * | |
| 118 | * @return The untitled text if the path hasn't been set. | |
| 119 | */ | |
| 120 | private Tooltip getTabTooltip() { | |
| 121 | final Path filePath = getPath(); | |
| 122 | return new Tooltip( filePath == null ? "" : filePath.toString() ); | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Returns a marker to indicate whether the file has been modified. | |
| 127 | * | |
| 128 | * @return "*" when the file has changed; otherwise null. | |
| 129 | */ | |
| 130 | private Text getModifiedMark() { | |
| 131 | return isModified() ? new Text( "*" ) : null; | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * Called when the user switches tab. | |
| 136 | */ | |
| 137 | private void activated() { | |
| 138 | // Tab is closed or no longer active. | |
| 139 | if( getTabPane() == null || !isSelected() ) { | |
| 140 | return; | |
| 141 | } | |
| 142 | ||
| 143 | // Switch to the tab without loading if the contents are already in memory. | |
| 144 | if( getContent() != null ) { | |
| 145 | getEditorPane().requestFocus(); | |
| 146 | return; | |
| 147 | } | |
| 148 | ||
| 149 | // Load the text and update the preview before the undo manager. | |
| 150 | load(); | |
| 151 | ||
| 152 | // Track undo requests -- can only be called *after* load. | |
| 153 | initUndoManager(); | |
| 154 | initLayout(); | |
| 155 | initFocus(); | |
| 156 | } | |
| 157 | ||
| 158 | private void initLayout() { | |
| 159 | setContent( getScrollPane() ); | |
| 160 | } | |
| 161 | ||
| 162 | private Node getScrollPane() { | |
| 163 | return getEditorPane().getScrollPane(); | |
| 164 | } | |
| 165 | ||
| 166 | private void initFocus() { | |
| 167 | getEditorPane().requestFocus(); | |
| 168 | } | |
| 169 | ||
| 170 | private void initUndoManager() { | |
| 171 | final UndoManager<?> undoManager = getUndoManager(); | |
| 172 | undoManager.forgetHistory(); | |
| 173 | ||
| 174 | // Bind the editor undo manager to the properties. | |
| 175 | mModified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) ); | |
| 176 | canUndo.bind( undoManager.undoAvailableProperty() ); | |
| 177 | canRedo.bind( undoManager.redoAvailableProperty() ); | |
| 178 | } | |
| 179 | ||
| 180 | /** | |
| 181 | * Searches from the caret position forward for the given string. | |
| 182 | * | |
| 183 | * @param needle The text string to match. | |
| 184 | */ | |
| 185 | public void searchNext( final String needle ) { | |
| 186 | final String haystack = getEditorText(); | |
| 187 | int index = haystack.indexOf( needle, getCaretPosition() ); | |
| 188 | ||
| 189 | // Wrap around. | |
| 190 | if( index == -1 ) { | |
| 191 | index = haystack.indexOf( needle ); | |
| 192 | } | |
| 193 | ||
| 194 | if( index >= 0 ) { | |
| 195 | setCaretPosition( index ); | |
| 196 | getEditor().selectRange( index, index + needle.length() ); | |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | /** | |
| 201 | * Returns the index into the text where the caret blinks happily away. | |
| 202 | * | |
| 203 | * @return A number from 0 to the editor's document text length. | |
| 204 | */ | |
| 205 | public int getCaretPosition() { | |
| 206 | return getEditor().getCaretPosition(); | |
| 207 | } | |
| 208 | ||
| 209 | /** | |
| 210 | * Moves the caret to a given offset. | |
| 211 | * | |
| 212 | * @param offset The new caret offset. | |
| 213 | */ | |
| 214 | private void setCaretPosition( final int offset ) { | |
| 215 | getEditor().moveTo( offset ); | |
| 216 | getEditor().requestFollowCaret(); | |
| 217 | } | |
| 218 | ||
| 219 | /** | |
| 220 | * Returns the caret's current row and column position. | |
| 221 | * | |
| 222 | * @return The caret's offset into the document. | |
| 223 | */ | |
| 224 | public Position getCaretOffset() { | |
| 225 | return getEditor().offsetToPosition( getCaretPosition(), Forward ); | |
| 226 | } | |
| 227 | ||
| 228 | /** | |
| 229 | * Allows observers to synchronize caret position changes. | |
| 230 | * | |
| 231 | * @return An observable caret property value. | |
| 232 | */ | |
| 233 | public final ObservableValue<Integer> caretPositionProperty() { | |
| 234 | return getEditor().caretPositionProperty(); | |
| 235 | } | |
| 236 | ||
| 237 | /** | |
| 238 | * Returns the text area associated with this tab. | |
| 239 | * | |
| 240 | * @return A text editor. | |
| 241 | */ | |
| 242 | private StyleClassedTextArea getEditor() { | |
| 243 | return getEditorPane().getEditor(); | |
| 244 | } | |
| 245 | ||
| 246 | /** | |
| 247 | * Returns true if the given path exactly matches this tab's path. | |
| 248 | * | |
| 249 | * @param check The path to compare against. | |
| 250 | * @return true The paths are the same. | |
| 251 | */ | |
| 252 | public boolean isPath( final Path check ) { | |
| 253 | final Path filePath = getPath(); | |
| 254 | ||
| 255 | return filePath != null && filePath.equals( check ); | |
| 256 | } | |
| 257 | ||
| 258 | /** | |
| 259 | * Reads the entire file contents from the path associated with this tab. | |
| 260 | */ | |
| 261 | private void load() { | |
| 262 | final Path path = getPath(); | |
| 263 | final File file = path.toFile(); | |
| 264 | ||
| 265 | try { | |
| 266 | if( file.exists() ) { | |
| 267 | if( file.canWrite() && file.canRead() ) { | |
| 268 | final EditorPane pane = getEditorPane(); | |
| 269 | pane.setText( asString( Files.readAllBytes( path ) ) ); | |
| 270 | pane.scrollToTop(); | |
| 271 | } | |
| 272 | else { | |
| 273 | final String msg = get( | |
| 274 | "FileEditor.loadFailed.message", | |
| 275 | file.toString(), | |
| 276 | get( "FileEditor.loadFailed.reason.permissions" ) | |
| 277 | ); | |
| 278 | getNotifier().notify( msg ); | |
| 279 | } | |
| 280 | } | |
| 281 | } catch( final Exception ex ) { | |
| 282 | getNotifier().notify( ex ); | |
| 283 | } | |
| 284 | } | |
| 285 | ||
| 286 | /** | |
| 287 | * Saves the entire file contents from the path associated with this tab. | |
| 288 | * | |
| 289 | * @return true The file has been saved. | |
| 290 | */ | |
| 291 | public boolean save() { | |
| 292 | try { | |
| 293 | final EditorPane editor = getEditorPane(); | |
| 294 | Files.write( getPath(), asBytes( editor.getText() ) ); | |
| 295 | editor.getUndoManager().mark(); | |
| 296 | return true; | |
| 297 | } catch( final Exception ex ) { | |
| 298 | return alert( | |
| 299 | "FileEditor.saveFailed.title", | |
| 300 | "FileEditor.saveFailed.message", | |
| 301 | ex | |
| 302 | ); | |
| 303 | } | |
| 304 | } | |
| 305 | ||
| 306 | /** | |
| 307 | * Creates an alert dialog and waits for it to close. | |
| 308 | * | |
| 309 | * @param titleKey Resource bundle key for the alert dialog title. | |
| 310 | * @param messageKey Resource bundle key for the alert dialog message. | |
| 311 | * @param e The unexpected happening. | |
| 312 | * @return false | |
| 313 | */ | |
| 314 | @SuppressWarnings("SameParameterValue") | |
| 315 | private boolean alert( | |
| 316 | final String titleKey, final String messageKey, final Exception e ) { | |
| 317 | final Notifier service = getNotifier(); | |
| 318 | final Path filePath = getPath(); | |
| 319 | ||
| 320 | final Notification message = service.createNotification( | |
| 321 | get( titleKey ), | |
| 322 | get( messageKey ), | |
| 323 | filePath == null ? "" : filePath, | |
| 324 | e.getMessage() | |
| 325 | ); | |
| 326 | ||
| 327 | try { | |
| 328 | service.createError( getWindow(), message ).showAndWait(); | |
| 329 | } catch( final Exception ex ) { | |
| 330 | getNotifier().notify( ex ); | |
| 331 | } | |
| 332 | ||
| 333 | return false; | |
| 334 | } | |
| 335 | ||
| 336 | private Window getWindow() { | |
| 337 | final Scene scene = getEditorPane().getScene(); | |
| 338 | ||
| 339 | if( scene == null ) { | |
| 340 | throw new UnsupportedOperationException( "No scene window available" ); | |
| 341 | } | |
| 342 | ||
| 343 | return scene.getWindow(); | |
| 344 | } | |
| 345 | ||
| 346 | /** | |
| 347 | * Returns a best guess at the file encoding. If the encoding could not be | |
| 348 | * detected, this will return the default charset for the JVM. | |
| 349 | * | |
| 350 | * @param bytes The bytes to perform character encoding detection. | |
| 351 | * @return The character encoding. | |
| 352 | */ | |
| 353 | private Charset detectEncoding( final byte[] bytes ) { | |
| 354 | final var detector = new UniversalDetector( null ); | |
| 355 | detector.handleData( bytes, 0, bytes.length ); | |
| 356 | detector.dataEnd(); | |
| 357 | ||
| 358 | final String charset = detector.getDetectedCharset(); | |
| 359 | ||
| 360 | return charset == null | |
| 361 | ? Charset.defaultCharset() | |
| 362 | : Charset.forName( charset.toUpperCase( ENGLISH ) ); | |
| 363 | } | |
| 364 | ||
| 365 | /** | |
| 366 | * Converts the given string to an array of bytes using the encoding that was | |
| 367 | * originally detected (if any) and associated with this file. | |
| 368 | * | |
| 369 | * @param text The text to convert into the original file encoding. | |
| 370 | * @return A series of bytes ready for writing to a file. | |
| 371 | */ | |
| 372 | private byte[] asBytes( final String text ) { | |
| 373 | return text.getBytes( getEncoding() ); | |
| 374 | } | |
| 375 | ||
| 376 | /** | |
| 377 | * Converts the given bytes into a Java String. This will call setEncoding | |
| 378 | * with the encoding detected by the CharsetDetector. | |
| 379 | * | |
| 380 | * @param text The text of unknown character encoding. | |
| 381 | * @return The text, in its auto-detected encoding, as a String. | |
| 382 | */ | |
| 383 | private String asString( final byte[] text ) { | |
| 384 | setEncoding( detectEncoding( text ) ); | |
| 385 | return new String( text, getEncoding() ); | |
| 386 | } | |
| 387 | ||
| 388 | /** | |
| 389 | * Returns the path to the file being edited in this tab. | |
| 390 | * | |
| 391 | * @return A non-null instance. | |
| 392 | */ | |
| 393 | public Path getPath() { | |
| 394 | return mPath; | |
| 395 | } | |
| 396 | ||
| 397 | /** | |
| 398 | * Sets the path to a file for editing and then updates the tab with the | |
| 399 | * file contents. | |
| 400 | * | |
| 401 | * @param path A non-null instance. | |
| 402 | */ | |
| 403 | public void setPath( final Path path ) { | |
| 404 | assert path != null; | |
| 405 | ||
| 406 | mPath = path; | |
| 407 | ||
| 408 | updateTab(); | |
| 409 | } | |
| 410 | ||
| 411 | public boolean isModified() { | |
| 412 | return mModified.get(); | |
| 413 | } | |
| 414 | ||
| 415 | ReadOnlyBooleanProperty modifiedProperty() { | |
| 416 | return mModified.getReadOnlyProperty(); | |
| 417 | } | |
| 418 | ||
| 419 | BooleanProperty canUndoProperty() { | |
| 420 | return this.canUndo; | |
| 421 | } | |
| 422 | ||
| 423 | BooleanProperty canRedoProperty() { | |
| 424 | return this.canRedo; | |
| 425 | } | |
| 426 | ||
| 427 | private UndoManager<?> getUndoManager() { | |
| 428 | return getEditorPane().getUndoManager(); | |
| 429 | } | |
| 430 | ||
| 431 | /** | |
| 432 | * Forwards to the editor pane's listeners for text change events. | |
| 433 | * | |
| 434 | * @param listener The listener to notify when the text changes. | |
| 435 | */ | |
| 436 | public void addTextChangeListener( final ChangeListener<String> listener ) { | |
| 437 | getEditorPane().addTextChangeListener( listener ); | |
| 438 | } | |
| 439 | ||
| 440 | /** | |
| 441 | * Forwards to the editor pane's listeners for caret paragraph change events. | |
| 442 | * | |
| 443 | * @param listener The listener to notify when the caret changes paragraphs. | |
| 444 | */ | |
| 445 | public void addCaretParagraphListener( | |
| 446 | final ChangeListener<Integer> listener ) { | |
| 447 | getEditorPane().addCaretParagraphListener( listener ); | |
| 448 | } | |
| 449 | ||
| 450 | public <T extends Event> void addEventFilter( | |
| 451 | final EventType<T> eventType, | |
| 452 | final EventHandler<? super T> eventFilter ) { | |
| 453 | getEditorPane().getEditor().addEventFilter( eventType, eventFilter ); | |
| 454 | } | |
| 455 | ||
| 456 | /** | |
| 457 | * Forwards the request to the editor pane. | |
| 458 | * | |
| 459 | * @return The text to process. | |
| 460 | */ | |
| 461 | public String getEditorText() { | |
| 462 | return getEditorPane().getText(); | |
| 463 | } | |
| 464 | ||
| 465 | /** | |
| 466 | * Returns the editor pane, or creates one if it doesn't yet exist. | |
| 467 | * | |
| 468 | * @return The editor pane, never null. | |
| 469 | */ | |
| 470 | public EditorPane getEditorPane() { | |
| 471 | return mEditorPane; | |
| 472 | } | |
| 473 | ||
| 474 | /** | |
| 475 | * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been | |
| 476 | * determined. | |
| 477 | * | |
| 478 | * @return The file encoding or UTF-8 if unknown. | |
| 479 | */ | |
| 480 | private Charset getEncoding() { | |
| 481 | return mEncoding; | |
| 482 | } | |
| 483 | ||
| 484 | private void setEncoding( final Charset encoding ) { | |
| 485 | assert encoding != null; | |
| 486 | ||
| 487 | mEncoding = encoding; | |
| 488 | } | |
| 489 | ||
| 490 | private Notifier getNotifier() { | |
| 491 | return mNotifier; | |
| 492 | } | |
| 493 | ||
| 494 | /** | |
| 495 | * Returns the tab title, without any modified indicators. | |
| 496 | * | |
| 497 | * @return The tab title. | |
| 498 | */ | |
| 499 | @Override | |
| 500 | public String toString() { | |
| 501 | return getTabTitle(); | |
| 502 | } | |
| 503 | } | |
| 1 | 504 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.predicates.files.FileTypePredicate; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import com.scrivenvar.service.Settings; | |
| 33 | import com.scrivenvar.service.events.Notification; | |
| 34 | import com.scrivenvar.service.events.Notifier; | |
| 35 | import com.scrivenvar.util.Utils; | |
| 36 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 37 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 38 | import javafx.beans.property.ReadOnlyObjectProperty; | |
| 39 | import javafx.beans.property.ReadOnlyObjectWrapper; | |
| 40 | import javafx.beans.value.ChangeListener; | |
| 41 | import javafx.beans.value.ObservableValue; | |
| 42 | import javafx.collections.ListChangeListener; | |
| 43 | import javafx.collections.ObservableList; | |
| 44 | import javafx.event.Event; | |
| 45 | import javafx.scene.Node; | |
| 46 | import javafx.scene.control.Alert; | |
| 47 | import javafx.scene.control.ButtonType; | |
| 48 | import javafx.scene.control.Tab; | |
| 49 | import javafx.scene.control.TabPane; | |
| 50 | import javafx.stage.FileChooser; | |
| 51 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 52 | import javafx.stage.Window; | |
| 53 | ||
| 54 | import java.io.File; | |
| 55 | import java.nio.file.Path; | |
| 56 | import java.util.ArrayList; | |
| 57 | import java.util.List; | |
| 58 | import java.util.Optional; | |
| 59 | import java.util.concurrent.atomic.AtomicReference; | |
| 60 | import java.util.function.Consumer; | |
| 61 | import java.util.prefs.Preferences; | |
| 62 | import java.util.stream.Collectors; | |
| 63 | ||
| 64 | import static com.scrivenvar.Constants.GLOB_PREFIX_FILE; | |
| 65 | import static com.scrivenvar.FileType.*; | |
| 66 | import static com.scrivenvar.Messages.get; | |
| 67 | import static com.scrivenvar.service.events.Notifier.YES; | |
| 68 | ||
| 69 | /** | |
| 70 | * Tab pane for file editors. | |
| 71 | * | |
| 72 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 73 | */ | |
| 74 | public final class FileEditorTabPane extends TabPane { | |
| 75 | ||
| 76 | private final static String FILTER_EXTENSION_TITLES = | |
| 77 | "Dialog.file.choose.filter"; | |
| 78 | ||
| 79 | private final static Options sOptions = Services.load( Options.class ); | |
| 80 | private final static Settings sSettings = Services.load( Settings.class ); | |
| 81 | private final static Notifier sNotifier = Services.load( Notifier.class ); | |
| 82 | ||
| 83 | private final ReadOnlyObjectWrapper<Path> openDefinition = | |
| 84 | new ReadOnlyObjectWrapper<>(); | |
| 85 | private final ReadOnlyObjectWrapper<FileEditorTab> mActiveFileEditor = | |
| 86 | new ReadOnlyObjectWrapper<>(); | |
| 87 | private final ReadOnlyBooleanWrapper anyFileEditorModified = | |
| 88 | new ReadOnlyBooleanWrapper(); | |
| 89 | private final Consumer<Double> mScrollEventObserver; | |
| 90 | ||
| 91 | /** | |
| 92 | * Constructs a new file editor tab pane. | |
| 93 | */ | |
| 94 | public FileEditorTabPane( final Consumer<Double> scrollEventObserver ) { | |
| 95 | final ObservableList<Tab> tabs = getTabs(); | |
| 96 | ||
| 97 | setFocusTraversable( false ); | |
| 98 | setTabClosingPolicy( TabClosingPolicy.ALL_TABS ); | |
| 99 | ||
| 100 | addTabSelectionListener( | |
| 101 | ( ObservableValue<? extends Tab> tabPane, | |
| 102 | final Tab oldTab, final Tab newTab ) -> { | |
| 103 | ||
| 104 | if( newTab != null ) { | |
| 105 | mActiveFileEditor.set( (FileEditorTab) newTab ); | |
| 106 | } | |
| 107 | } | |
| 108 | ); | |
| 109 | ||
| 110 | final ChangeListener<Boolean> modifiedListener = ( observable, oldValue, | |
| 111 | newValue ) -> { | |
| 112 | for( final Tab tab : tabs ) { | |
| 113 | if( ((FileEditorTab) tab).isModified() ) { | |
| 114 | this.anyFileEditorModified.set( true ); | |
| 115 | break; | |
| 116 | } | |
| 117 | } | |
| 118 | }; | |
| 119 | ||
| 120 | tabs.addListener( | |
| 121 | (ListChangeListener<Tab>) change -> { | |
| 122 | while( change.next() ) { | |
| 123 | if( change.wasAdded() ) { | |
| 124 | change.getAddedSubList().forEach( | |
| 125 | ( tab ) -> ((FileEditorTab) tab).modifiedProperty() | |
| 126 | .addListener( modifiedListener ) ); | |
| 127 | } | |
| 128 | else if( change.wasRemoved() ) { | |
| 129 | change.getRemoved().forEach( | |
| 130 | ( tab ) -> ((FileEditorTab) tab).modifiedProperty() | |
| 131 | .removeListener( | |
| 132 | modifiedListener ) ); | |
| 133 | } | |
| 134 | } | |
| 135 | ||
| 136 | // Changes in the tabs may also change anyFileEditorModified property | |
| 137 | // (e.g. closed modified file) | |
| 138 | modifiedListener.changed( null, null, null ); | |
| 139 | } | |
| 140 | ); | |
| 141 | ||
| 142 | mScrollEventObserver = scrollEventObserver; | |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * Allows observers to be notified when the current file editor tab changes. | |
| 147 | * | |
| 148 | * @param listener The listener to notify of tab change events. | |
| 149 | */ | |
| 150 | public void addTabSelectionListener( final ChangeListener<Tab> listener ) { | |
| 151 | // Observe the tab so that when a new tab is opened or selected, | |
| 152 | // a notification is kicked off. | |
| 153 | getSelectionModel().selectedItemProperty().addListener( listener ); | |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Returns the tab that has keyboard focus. | |
| 158 | * | |
| 159 | * @return A non-null instance. | |
| 160 | */ | |
| 161 | public FileEditorTab getActiveFileEditor() { | |
| 162 | return mActiveFileEditor.get(); | |
| 163 | } | |
| 164 | ||
| 165 | /** | |
| 166 | * Returns the property corresponding to the tab that has focus. | |
| 167 | * | |
| 168 | * @return A non-null instance. | |
| 169 | */ | |
| 170 | public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() { | |
| 171 | return mActiveFileEditor.getReadOnlyProperty(); | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Property that can answer whether the text has been modified. | |
| 176 | * | |
| 177 | * @return A non-null instance, true meaning the content has not been saved. | |
| 178 | */ | |
| 179 | ReadOnlyBooleanProperty anyFileEditorModifiedProperty() { | |
| 180 | return this.anyFileEditorModified.getReadOnlyProperty(); | |
| 181 | } | |
| 182 | ||
| 183 | /** | |
| 184 | * Creates a new editor instance from the given path. | |
| 185 | * | |
| 186 | * @param path The file to open. | |
| 187 | * @return A non-null instance. | |
| 188 | */ | |
| 189 | private FileEditorTab createFileEditor( final Path path ) { | |
| 190 | assert path != null; | |
| 191 | ||
| 192 | final FileEditorTab tab = new FileEditorTab( path ); | |
| 193 | ||
| 194 | tab.getEditorPane().getScrollPane().estimatedScrollYProperty().addObserver( | |
| 195 | mScrollEventObserver | |
| 196 | ); | |
| 197 | ||
| 198 | tab.setOnCloseRequest( e -> { | |
| 199 | if( !canCloseEditor( tab ) ) { | |
| 200 | e.consume(); | |
| 201 | } | |
| 202 | else if( isActiveFileEditor( tab ) ) { | |
| 203 | // Prevent prompting the user to save when there are no file editor | |
| 204 | // tabs open. | |
| 205 | mActiveFileEditor.set( null ); | |
| 206 | } | |
| 207 | } ); | |
| 208 | ||
| 209 | return tab; | |
| 210 | } | |
| 211 | ||
| 212 | private boolean isActiveFileEditor( final FileEditorTab tab ) { | |
| 213 | return getActiveFileEditor() == tab; | |
| 214 | } | |
| 215 | ||
| 216 | private Path getDefaultPath() { | |
| 217 | final String filename = getDefaultFilename(); | |
| 218 | return (new File( filename )).toPath(); | |
| 219 | } | |
| 220 | ||
| 221 | private String getDefaultFilename() { | |
| 222 | return getSettings().getSetting( "file.default", "untitled.md" ); | |
| 223 | } | |
| 224 | ||
| 225 | /** | |
| 226 | * Called when the user selects New from the File menu. | |
| 227 | */ | |
| 228 | void newEditor() { | |
| 229 | final Path defaultPath = getDefaultPath(); | |
| 230 | final FileEditorTab tab = createFileEditor( defaultPath ); | |
| 231 | ||
| 232 | getTabs().add( tab ); | |
| 233 | getSelectionModel().select( tab ); | |
| 234 | } | |
| 235 | ||
| 236 | void openFileDialog() { | |
| 237 | final String title = get( "Dialog.file.choose.open.title" ); | |
| 238 | final FileChooser dialog = createFileChooser( title ); | |
| 239 | final List<File> files = dialog.showOpenMultipleDialog( getWindow() ); | |
| 240 | ||
| 241 | if( files != null ) { | |
| 242 | openFiles( files ); | |
| 243 | } | |
| 244 | } | |
| 245 | ||
| 246 | /** | |
| 247 | * Opens the files into new editors, unless one of those files was a | |
| 248 | * definition file. The definition file is loaded into the definition pane, | |
| 249 | * but only the first one selected (multiple definition files will result in a | |
| 250 | * warning). | |
| 251 | * | |
| 252 | * @param files The list of non-definition files that the were requested to | |
| 253 | * open. | |
| 254 | */ | |
| 255 | private void openFiles( final List<File> files ) { | |
| 256 | final List<String> extensions = | |
| 257 | createExtensionFilter( DEFINITION ).getExtensions(); | |
| 258 | final FileTypePredicate predicate = | |
| 259 | new FileTypePredicate( extensions ); | |
| 260 | ||
| 261 | // The user might have opened multiple definitions files. These will | |
| 262 | // be discarded from the text editable files. | |
| 263 | final List<File> definitions | |
| 264 | = files.stream().filter( predicate ).collect( Collectors.toList() ); | |
| 265 | ||
| 266 | // Create a modifiable list to remove any definition files that were | |
| 267 | // opened. | |
| 268 | final List<File> editors = new ArrayList<>( files ); | |
| 269 | ||
| 270 | if( !editors.isEmpty() ) { | |
| 271 | saveLastDirectory( editors.get( 0 ) ); | |
| 272 | } | |
| 273 | ||
| 274 | editors.removeAll( definitions ); | |
| 275 | ||
| 276 | // Open editor-friendly files (e.g,. Markdown, XML) in new tabs. | |
| 277 | if( !editors.isEmpty() ) { | |
| 278 | openEditors( editors, 0 ); | |
| 279 | } | |
| 280 | ||
| 281 | if( !definitions.isEmpty() ) { | |
| 282 | openDefinition( definitions.get( 0 ) ); | |
| 283 | } | |
| 284 | } | |
| 285 | ||
| 286 | private void openEditors( final List<File> files, final int activeIndex ) { | |
| 287 | final int fileTally = files.size(); | |
| 288 | final List<Tab> tabs = getTabs(); | |
| 289 | ||
| 290 | // Close single unmodified "Untitled" tab. | |
| 291 | if( tabs.size() == 1 ) { | |
| 292 | final FileEditorTab fileEditor = (FileEditorTab) (tabs.get( 0 )); | |
| 293 | ||
| 294 | if( fileEditor.getPath() == null && !fileEditor.isModified() ) { | |
| 295 | closeEditor( fileEditor, false ); | |
| 296 | } | |
| 297 | } | |
| 298 | ||
| 299 | for( int i = 0; i < fileTally; i++ ) { | |
| 300 | final Path path = files.get( i ).toPath(); | |
| 301 | ||
| 302 | FileEditorTab fileEditorTab = findEditor( path ); | |
| 303 | ||
| 304 | // Only open new files. | |
| 305 | if( fileEditorTab == null ) { | |
| 306 | fileEditorTab = createFileEditor( path ); | |
| 307 | getTabs().add( fileEditorTab ); | |
| 308 | } | |
| 309 | ||
| 310 | // Select the first file in the list. | |
| 311 | if( i == activeIndex ) { | |
| 312 | getSelectionModel().select( fileEditorTab ); | |
| 313 | } | |
| 314 | } | |
| 315 | } | |
| 316 | ||
| 317 | /** | |
| 318 | * Returns a property that changes when a new definition file is opened. | |
| 319 | * | |
| 320 | * @return The path to a definition file that was opened. | |
| 321 | */ | |
| 322 | public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() { | |
| 323 | return getOnOpenDefinitionFile().getReadOnlyProperty(); | |
| 324 | } | |
| 325 | ||
| 326 | private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() { | |
| 327 | return this.openDefinition; | |
| 328 | } | |
| 329 | ||
| 330 | /** | |
| 331 | * Called when the user has opened a definition file (using the file open | |
| 332 | * dialog box). This will replace the current set of definitions for the | |
| 333 | * active tab. | |
| 334 | * | |
| 335 | * @param definition The file to open. | |
| 336 | */ | |
| 337 | private void openDefinition( final File definition ) { | |
| 338 | // TODO: Prevent reading this file twice when a new text document is opened. | |
| 339 | // (might be a matter of checking the value first). | |
| 340 | getOnOpenDefinitionFile().set( definition.toPath() ); | |
| 341 | } | |
| 342 | ||
| 343 | /** | |
| 344 | * Called when the contents of the editor are to be saved. | |
| 345 | * | |
| 346 | * @param tab The tab containing content to save. | |
| 347 | * @return true The contents were saved (or needn't be saved). | |
| 348 | */ | |
| 349 | public boolean saveEditor( final FileEditorTab tab ) { | |
| 350 | if( tab == null || !tab.isModified() ) { | |
| 351 | return true; | |
| 352 | } | |
| 353 | ||
| 354 | return tab.getPath() == null ? saveEditorAs( tab ) : tab.save(); | |
| 355 | } | |
| 356 | ||
| 357 | /** | |
| 358 | * Opens the Save As dialog for the user to save the content under a new | |
| 359 | * path. | |
| 360 | * | |
| 361 | * @param tab The tab with contents to save. | |
| 362 | * @return true The contents were saved, or the tab was null. | |
| 363 | */ | |
| 364 | public boolean saveEditorAs( final FileEditorTab tab ) { | |
| 365 | if( tab == null ) { | |
| 366 | return true; | |
| 367 | } | |
| 368 | ||
| 369 | getSelectionModel().select( tab ); | |
| 370 | ||
| 371 | final FileChooser fileChooser = createFileChooser( get( | |
| 372 | "Dialog.file.choose.save.title" ) ); | |
| 373 | final File file = fileChooser.showSaveDialog( getWindow() ); | |
| 374 | if( file == null ) { | |
| 375 | return false; | |
| 376 | } | |
| 377 | ||
| 378 | saveLastDirectory( file ); | |
| 379 | tab.setPath( file.toPath() ); | |
| 380 | ||
| 381 | return tab.save(); | |
| 382 | } | |
| 383 | ||
| 384 | void saveAllEditors() { | |
| 385 | for( final FileEditorTab fileEditor : getAllEditors() ) { | |
| 386 | saveEditor( fileEditor ); | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | /** | |
| 391 | * Answers whether the file has had modifications. ' | |
| 392 | * | |
| 393 | * @param tab THe tab to check for modifications. | |
| 394 | * @return false The file is unmodified. | |
| 395 | */ | |
| 396 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | |
| 397 | boolean canCloseEditor( final FileEditorTab tab ) { | |
| 398 | final AtomicReference<Boolean> canClose = new AtomicReference<>(); | |
| 399 | canClose.set( true ); | |
| 400 | ||
| 401 | if( tab.isModified() ) { | |
| 402 | final Notification message = getNotifyService().createNotification( | |
| 403 | Messages.get( "Alert.file.close.title" ), | |
| 404 | Messages.get( "Alert.file.close.text" ), | |
| 405 | tab.getText() | |
| 406 | ); | |
| 407 | ||
| 408 | final Alert confirmSave = getNotifyService().createConfirmation( | |
| 409 | getWindow(), message ); | |
| 410 | ||
| 411 | final Optional<ButtonType> buttonType = confirmSave.showAndWait(); | |
| 412 | ||
| 413 | buttonType.ifPresent( | |
| 414 | save -> canClose.set( | |
| 415 | save == YES ? saveEditor( tab ) : save == ButtonType.NO | |
| 416 | ) | |
| 417 | ); | |
| 418 | } | |
| 419 | ||
| 420 | return canClose.get(); | |
| 421 | } | |
| 422 | ||
| 423 | boolean closeEditor( final FileEditorTab tab, final boolean save ) { | |
| 424 | if( tab == null ) { | |
| 425 | return true; | |
| 426 | } | |
| 427 | ||
| 428 | if( save ) { | |
| 429 | Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT ); | |
| 430 | Event.fireEvent( tab, event ); | |
| 431 | ||
| 432 | if( event.isConsumed() ) { | |
| 433 | return false; | |
| 434 | } | |
| 435 | } | |
| 436 | ||
| 437 | getTabs().remove( tab ); | |
| 438 | ||
| 439 | if( tab.getOnClosed() != null ) { | |
| 440 | Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) ); | |
| 441 | } | |
| 442 | ||
| 443 | return true; | |
| 444 | } | |
| 445 | ||
| 446 | boolean closeAllEditors() { | |
| 447 | final FileEditorTab[] allEditors = getAllEditors(); | |
| 448 | final FileEditorTab activeEditor = getActiveFileEditor(); | |
| 449 | ||
| 450 | // try to save active tab first because in case the user decides to cancel, | |
| 451 | // then it stays active | |
| 452 | if( activeEditor != null && !canCloseEditor( activeEditor ) ) { | |
| 453 | return false; | |
| 454 | } | |
| 455 | ||
| 456 | // This should be called any time a tab changes. | |
| 457 | persistPreferences(); | |
| 458 | ||
| 459 | // save modified tabs | |
| 460 | for( int i = 0; i < allEditors.length; i++ ) { | |
| 461 | final FileEditorTab fileEditor = allEditors[ i ]; | |
| 462 | ||
| 463 | if( fileEditor == activeEditor ) { | |
| 464 | continue; | |
| 465 | } | |
| 466 | ||
| 467 | if( fileEditor.isModified() ) { | |
| 468 | // activate the modified tab to make its modified content visible to | |
| 469 | // the user | |
| 470 | getSelectionModel().select( i ); | |
| 471 | ||
| 472 | if( !canCloseEditor( fileEditor ) ) { | |
| 473 | return false; | |
| 474 | } | |
| 475 | } | |
| 476 | } | |
| 477 | ||
| 478 | // Close all tabs. | |
| 479 | for( final FileEditorTab fileEditor : allEditors ) { | |
| 480 | if( !closeEditor( fileEditor, false ) ) { | |
| 481 | return false; | |
| 482 | } | |
| 483 | } | |
| 484 | ||
| 485 | return getTabs().isEmpty(); | |
| 486 | } | |
| 487 | ||
| 488 | private FileEditorTab[] getAllEditors() { | |
| 489 | final ObservableList<Tab> tabs = getTabs(); | |
| 490 | final int length = tabs.size(); | |
| 491 | final FileEditorTab[] allEditors = new FileEditorTab[ length ]; | |
| 492 | ||
| 493 | for( int i = 0; i < length; i++ ) { | |
| 494 | allEditors[ i ] = (FileEditorTab) tabs.get( i ); | |
| 495 | } | |
| 496 | ||
| 497 | return allEditors; | |
| 498 | } | |
| 499 | ||
| 500 | /** | |
| 501 | * Returns the file editor tab that has the given path. | |
| 502 | * | |
| 503 | * @return null No file editor tab for the given path was found. | |
| 504 | */ | |
| 505 | private FileEditorTab findEditor( final Path path ) { | |
| 506 | for( final Tab tab : getTabs() ) { | |
| 507 | final FileEditorTab fileEditor = (FileEditorTab) tab; | |
| 508 | ||
| 509 | if( fileEditor.isPath( path ) ) { | |
| 510 | return fileEditor; | |
| 511 | } | |
| 512 | } | |
| 513 | ||
| 514 | return null; | |
| 515 | } | |
| 516 | ||
| 517 | private FileChooser createFileChooser( String title ) { | |
| 518 | final FileChooser fileChooser = new FileChooser(); | |
| 519 | ||
| 520 | fileChooser.setTitle( title ); | |
| 521 | fileChooser.getExtensionFilters().addAll( | |
| 522 | createExtensionFilters() ); | |
| 523 | ||
| 524 | final String lastDirectory = getPreferences().get( "lastDirectory", null ); | |
| 525 | File file = new File( (lastDirectory != null) ? lastDirectory : "." ); | |
| 526 | ||
| 527 | if( !file.isDirectory() ) { | |
| 528 | file = new File( "." ); | |
| 529 | } | |
| 530 | ||
| 531 | fileChooser.setInitialDirectory( file ); | |
| 532 | return fileChooser; | |
| 533 | } | |
| 534 | ||
| 535 | private List<ExtensionFilter> createExtensionFilters() { | |
| 536 | final List<ExtensionFilter> list = new ArrayList<>(); | |
| 537 | ||
| 538 | // TODO: Return a list of all properties that match the filter prefix. | |
| 539 | // This will allow dynamic filters to be added and removed just by | |
| 540 | // updating the properties file. | |
| 541 | list.add( createExtensionFilter( ALL ) ); | |
| 542 | list.add( createExtensionFilter( SOURCE ) ); | |
| 543 | list.add( createExtensionFilter( DEFINITION ) ); | |
| 544 | list.add( createExtensionFilter( XML ) ); | |
| 545 | return list; | |
| 546 | } | |
| 547 | ||
| 548 | /** | |
| 549 | * Returns a filter for file name extensions recognized by the application | |
| 550 | * that can be opened by the user. | |
| 551 | * | |
| 552 | * @param filetype Used to find the globbing pattern for extensions. | |
| 553 | * @return A filename filter suitable for use by a FileDialog instance. | |
| 554 | */ | |
| 555 | private ExtensionFilter createExtensionFilter( final FileType filetype ) { | |
| 556 | final String tKey = String.format( "%s.title.%s", | |
| 557 | FILTER_EXTENSION_TITLES, | |
| 558 | filetype ); | |
| 559 | final String eKey = String.format( "%s.%s", GLOB_PREFIX_FILE, filetype ); | |
| 560 | ||
| 561 | return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) ); | |
| 562 | } | |
| 563 | ||
| 564 | private List<String> getExtensions( final String key ) { | |
| 565 | return getSettings().getStringSettingList( key ); | |
| 566 | } | |
| 567 | ||
| 568 | private void saveLastDirectory( final File file ) { | |
| 569 | getPreferences().put( "lastDirectory", file.getParent() ); | |
| 570 | } | |
| 571 | ||
| 572 | public void restorePreferences() { | |
| 573 | int activeIndex = 0; | |
| 574 | ||
| 575 | final Preferences preferences = getPreferences(); | |
| 576 | final String[] fileNames = Utils.getPrefsStrings( preferences, "file" ); | |
| 577 | final String activeFileName = preferences.get( "activeFile", null ); | |
| 578 | ||
| 579 | final List<File> files = new ArrayList<>( fileNames.length ); | |
| 580 | ||
| 581 | for( final String fileName : fileNames ) { | |
| 582 | final File file = new File( fileName ); | |
| 583 | ||
| 584 | if( file.exists() ) { | |
| 585 | files.add( file ); | |
| 586 | ||
| 587 | if( fileName.equals( activeFileName ) ) { | |
| 588 | activeIndex = files.size() - 1; | |
| 589 | } | |
| 590 | } | |
| 591 | } | |
| 592 | ||
| 593 | if( files.isEmpty() ) { | |
| 594 | newEditor(); | |
| 595 | } | |
| 596 | else { | |
| 597 | openEditors( files, activeIndex ); | |
| 598 | } | |
| 599 | } | |
| 600 | ||
| 601 | public void persistPreferences() { | |
| 602 | final ObservableList<Tab> allEditors = getTabs(); | |
| 603 | final List<String> fileNames = new ArrayList<>( allEditors.size() ); | |
| 604 | ||
| 605 | for( final Tab tab : allEditors ) { | |
| 606 | final FileEditorTab fileEditor = (FileEditorTab) tab; | |
| 607 | final Path filePath = fileEditor.getPath(); | |
| 608 | ||
| 609 | if( filePath != null ) { | |
| 610 | fileNames.add( filePath.toString() ); | |
| 611 | } | |
| 612 | } | |
| 613 | ||
| 614 | final Preferences preferences = getPreferences(); | |
| 615 | Utils.putPrefsStrings( preferences, | |
| 616 | "file", | |
| 617 | fileNames.toArray( new String[ 0 ] ) ); | |
| 618 | ||
| 619 | final FileEditorTab activeEditor = getActiveFileEditor(); | |
| 620 | final Path filePath = activeEditor == null ? null : activeEditor.getPath(); | |
| 621 | ||
| 622 | if( filePath == null ) { | |
| 623 | preferences.remove( "activeFile" ); | |
| 624 | } | |
| 625 | else { | |
| 626 | preferences.put( "activeFile", filePath.toString() ); | |
| 627 | } | |
| 628 | } | |
| 629 | ||
| 630 | private Notifier getNotifyService() { | |
| 631 | return sNotifier; | |
| 632 | } | |
| 633 | ||
| 634 | private Settings getSettings() { | |
| 635 | return sSettings; | |
| 636 | } | |
| 637 | ||
| 638 | protected Options getOptions() { | |
| 639 | return sOptions; | |
| 640 | } | |
| 641 | ||
| 642 | private Window getWindow() { | |
| 643 | return getScene().getWindow(); | |
| 644 | } | |
| 645 | ||
| 646 | private Preferences getPreferences() { | |
| 647 | return getOptions().getState(); | |
| 648 | } | |
| 649 | ||
| 650 | Node getNode() { | |
| 651 | return this; | |
| 652 | } | |
| 653 | } | |
| 1 | 654 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents different file type classifications. These are high-level mappings | |
| 32 | * that correspond to the list of glob patterns found within | |
| 33 | * settings.properties. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public enum FileType { | |
| 38 | ||
| 39 | ALL( "all" ), | |
| 40 | RMARKDOWN( "rmarkdown" ), | |
| 41 | RXML( "rxml" ), | |
| 42 | SOURCE( "source" ), | |
| 43 | DEFINITION( "definition" ), | |
| 44 | XML( "xml" ), | |
| 45 | CSV( "csv" ), | |
| 46 | JSON( "json" ), | |
| 47 | TOML( "toml" ), | |
| 48 | YAML( "yaml" ), | |
| 49 | PROPERTIES( "properties" ); | |
| 50 | ||
| 51 | private final String mType; | |
| 52 | ||
| 53 | /** | |
| 54 | * Default constructor for enumerated file type. | |
| 55 | * | |
| 56 | * @param type Human-readable name for the file type. | |
| 57 | */ | |
| 58 | FileType( final String type ) { | |
| 59 | mType = type; | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Returns the file type that corresponds to the given string. | |
| 64 | * | |
| 65 | * @param type The string to compare against this enumeration of file types. | |
| 66 | * | |
| 67 | * @return The corresponding File Type for the given string. | |
| 68 | * | |
| 69 | * @throws IllegalArgumentException Type not found. | |
| 70 | */ | |
| 71 | public static FileType from( final String type ) { | |
| 72 | for( final FileType fileType : FileType.values() ) { | |
| 73 | if( fileType.isType( type ) ) { | |
| 74 | return fileType; | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | throw new IllegalArgumentException( type ); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Answers whether this file type matches the given string, case insensitive | |
| 83 | * comparison. | |
| 84 | * | |
| 85 | * @param type Presumably a file name extension to check against. | |
| 86 | * | |
| 87 | * @return true The given extension corresponds to this enumerated type. | |
| 88 | */ | |
| 89 | public boolean isType( final String type ) { | |
| 90 | return getType().equalsIgnoreCase( type ); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Returns the human-readable name for the file type. | |
| 95 | * | |
| 96 | * @return A non-null instance. | |
| 97 | */ | |
| 98 | private String getType() { | |
| 99 | return mType; | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Returns the lowercase version of the file name extension. | |
| 104 | * | |
| 105 | * @return The file name, in lower case. | |
| 106 | */ | |
| 107 | @Override | |
| 108 | public String toString() { | |
| 109 | return getType(); | |
| 110 | } | |
| 111 | } | |
| 1 | 112 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import java.io.IOException; | |
| 31 | import java.io.InputStream; | |
| 32 | import java.util.Calendar; | |
| 33 | import java.util.Properties; | |
| 34 | ||
| 35 | import static java.lang.String.format; | |
| 36 | ||
| 37 | /** | |
| 38 | * Launches the application using the {@link Main} class. | |
| 39 | * | |
| 40 | * <p> | |
| 41 | * This is required until modules are implemented, which may never happen | |
| 42 | * because the application should be ported away from Java and JavaFX. | |
| 43 | * </p> | |
| 44 | */ | |
| 45 | public class Launcher { | |
| 46 | /** | |
| 47 | * Delegates to the application entry point. | |
| 48 | * | |
| 49 | * @param args Command-line arguments. | |
| 50 | */ | |
| 51 | public static void main( final String[] args ) throws IOException { | |
| 52 | // Shhh. | |
| 53 | System.err.close(); | |
| 54 | ||
| 55 | showAppInfo(); | |
| 56 | Main.main( args ); | |
| 57 | } | |
| 58 | ||
| 59 | @SuppressWarnings("RedundantStringFormatCall") | |
| 60 | private static void showAppInfo() throws IOException { | |
| 61 | out( format( "%s version %s", getTitle(), getVersion() ) ); | |
| 62 | out( format( "Copyright %s by White Magic Software, Ltd.", getYear() ) ); | |
| 63 | out( format( "Portions copyright 2020 Karl Tauber." ) ); | |
| 64 | } | |
| 65 | ||
| 66 | private static void out( final String s ) { | |
| 67 | System.out.println( s ); | |
| 68 | } | |
| 69 | ||
| 70 | private static String getTitle() throws IOException { | |
| 71 | final Properties properties = loadProperties( "messages.properties" ); | |
| 72 | return properties.getProperty( "Main.title" ); | |
| 73 | } | |
| 74 | ||
| 75 | private static String getVersion() throws IOException { | |
| 76 | final Properties properties = loadProperties( "app.properties" ); | |
| 77 | return properties.getProperty( "application.version" ); | |
| 78 | } | |
| 79 | ||
| 80 | private static String getYear() { | |
| 81 | return Integer.toString( Calendar.getInstance().get( Calendar.YEAR ) ); | |
| 82 | } | |
| 83 | ||
| 84 | @SuppressWarnings("SameParameterValue") | |
| 85 | private static Properties loadProperties( final String resource ) | |
| 86 | throws IOException { | |
| 87 | final Properties properties = new Properties(); | |
| 88 | properties.load( getResourceAsStream( getResourceName( resource ) ) ); | |
| 89 | return properties; | |
| 90 | } | |
| 91 | ||
| 92 | private static String getResourceName( final String resource ) { | |
| 93 | return format( "%s/%s", getPackagePath(), resource ); | |
| 94 | } | |
| 95 | ||
| 96 | private static String getPackagePath() { | |
| 97 | return Launcher.class.getPackageName().replace( '.', '/' ); | |
| 98 | } | |
| 99 | ||
| 100 | private static InputStream getResourceAsStream( final String resource ) { | |
| 101 | return Launcher.class.getClassLoader().getResourceAsStream( resource ); | |
| 102 | } | |
| 103 | } | |
| 1 | 104 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.preferences.FilePreferencesFactory; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import com.scrivenvar.service.Snitch; | |
| 33 | import com.scrivenvar.service.events.Notifier; | |
| 34 | import com.scrivenvar.util.StageState; | |
| 35 | import javafx.application.Application; | |
| 36 | import javafx.scene.Scene; | |
| 37 | import javafx.scene.image.Image; | |
| 38 | import javafx.stage.Stage; | |
| 39 | ||
| 40 | import java.util.logging.LogManager; | |
| 41 | ||
| 42 | import static com.scrivenvar.Constants.*; | |
| 43 | import static com.scrivenvar.Messages.get; | |
| 44 | ||
| 45 | /** | |
| 46 | * Application entry point. The application allows users to edit Markdown | |
| 47 | * files and see a real-time preview of the edits. | |
| 48 | * | |
| 49 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 50 | */ | |
| 51 | public final class Main extends Application { | |
| 52 | ||
| 53 | // Suppress logging errors to standard output. | |
| 54 | static { | |
| 55 | LogManager.getLogManager().reset(); | |
| 56 | } | |
| 57 | ||
| 58 | private static Application sApplication; | |
| 59 | ||
| 60 | private final Options mOptions = Services.load( Options.class ); | |
| 61 | private final Notifier mNotifier = Services.load( Notifier.class ); | |
| 62 | private final Snitch mSnitch = Services.load( Snitch.class ); | |
| 63 | private final Thread mSnitchThread = new Thread( getSnitch() ); | |
| 64 | private final MainWindow mMainWindow = new MainWindow(); | |
| 65 | ||
| 66 | @SuppressWarnings({"FieldCanBeLocal", "unused"}) | |
| 67 | private StageState mStageState; | |
| 68 | ||
| 69 | /** | |
| 70 | * Application entry point. | |
| 71 | * | |
| 72 | * @param args Command-line arguments. | |
| 73 | */ | |
| 74 | public static void main( final String[] args ) { | |
| 75 | initPreferences(); | |
| 76 | launch( args ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * JavaFX entry point. | |
| 81 | * | |
| 82 | * @param stage The primary application stage. | |
| 83 | */ | |
| 84 | @Override | |
| 85 | public void start( final Stage stage ) { | |
| 86 | initApplication(); | |
| 87 | initNotifyService(); | |
| 88 | initState( stage ); | |
| 89 | initStage( stage ); | |
| 90 | initSnitch(); | |
| 91 | ||
| 92 | stage.show(); | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * Sets the factory used for reading user preferences. | |
| 97 | */ | |
| 98 | private static void initPreferences() { | |
| 99 | System.setProperty( | |
| 100 | "java.util.prefs.PreferencesFactory", | |
| 101 | FilePreferencesFactory.class.getName() | |
| 102 | ); | |
| 103 | } | |
| 104 | ||
| 105 | public static void showDocument( final String uri ) { | |
| 106 | getApplication().getHostServices().showDocument( uri ); | |
| 107 | } | |
| 108 | ||
| 109 | private void initApplication() { | |
| 110 | sApplication = this; | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Constructs the notify service and appends the main window to the list of | |
| 115 | * notification observers. | |
| 116 | */ | |
| 117 | private void initNotifyService() { | |
| 118 | mNotifier.addObserver( getMainWindow() ); | |
| 119 | } | |
| 120 | ||
| 121 | private void initState( final Stage stage ) { | |
| 122 | mStageState = new StageState( stage, getOptions().getState() ); | |
| 123 | } | |
| 124 | ||
| 125 | private void initStage( final Stage stage ) { | |
| 126 | stage.getIcons().addAll( | |
| 127 | createImage( FILE_LOGO_16 ), | |
| 128 | createImage( FILE_LOGO_32 ), | |
| 129 | createImage( FILE_LOGO_128 ), | |
| 130 | createImage( FILE_LOGO_256 ), | |
| 131 | createImage( FILE_LOGO_512 ) ); | |
| 132 | stage.setTitle( getApplicationTitle() ); | |
| 133 | stage.setScene( getScene() ); | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * Watch for file system changes. | |
| 138 | */ | |
| 139 | private void initSnitch() { | |
| 140 | getSnitchThread().start(); | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Stops the snitch service, if its running. | |
| 145 | * | |
| 146 | * @throws InterruptedException Couldn't stop the snitch thread. | |
| 147 | */ | |
| 148 | @Override | |
| 149 | public void stop() throws InterruptedException { | |
| 150 | getSnitch().stop(); | |
| 151 | ||
| 152 | final Thread thread = getSnitchThread(); | |
| 153 | thread.interrupt(); | |
| 154 | thread.join(); | |
| 155 | } | |
| 156 | ||
| 157 | private synchronized Snitch getSnitch() { | |
| 158 | return mSnitch; | |
| 159 | } | |
| 160 | ||
| 161 | private Thread getSnitchThread() { | |
| 162 | return mSnitchThread; | |
| 163 | } | |
| 164 | ||
| 165 | private synchronized Options getOptions() { | |
| 166 | return mOptions; | |
| 167 | } | |
| 168 | ||
| 169 | private Scene getScene() { | |
| 170 | return getMainWindow().getScene(); | |
| 171 | } | |
| 172 | ||
| 173 | private MainWindow getMainWindow() { | |
| 174 | return mMainWindow; | |
| 175 | } | |
| 176 | ||
| 177 | private static Application getApplication() { | |
| 178 | return sApplication; | |
| 179 | } | |
| 180 | ||
| 181 | private String getApplicationTitle() { | |
| 182 | return get( "Main.title" ); | |
| 183 | } | |
| 184 | ||
| 185 | private Image createImage( final String filename ) { | |
| 186 | return new Image( filename ); | |
| 187 | } | |
| 188 | } | |
| 1 | 189 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.definition.DefinitionFactory; | |
| 31 | import com.scrivenvar.definition.DefinitionPane; | |
| 32 | import com.scrivenvar.definition.DefinitionSource; | |
| 33 | import com.scrivenvar.definition.MapInterpolator; | |
| 34 | import com.scrivenvar.definition.yaml.YamlDefinitionSource; | |
| 35 | import com.scrivenvar.editors.EditorPane; | |
| 36 | import com.scrivenvar.editors.VariableNameInjector; | |
| 37 | import com.scrivenvar.editors.markdown.MarkdownEditorPane; | |
| 38 | import com.scrivenvar.preferences.UserPreferences; | |
| 39 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 40 | import com.scrivenvar.processors.Processor; | |
| 41 | import com.scrivenvar.processors.ProcessorFactory; | |
| 42 | import com.scrivenvar.service.Options; | |
| 43 | import com.scrivenvar.service.Snitch; | |
| 44 | import com.scrivenvar.service.events.Notifier; | |
| 45 | import com.scrivenvar.util.Action; | |
| 46 | import com.scrivenvar.util.ActionBuilder; | |
| 47 | import com.scrivenvar.util.ActionUtils; | |
| 48 | import javafx.application.Platform; | |
| 49 | import javafx.beans.binding.Bindings; | |
| 50 | import javafx.beans.binding.BooleanBinding; | |
| 51 | import javafx.beans.property.BooleanProperty; | |
| 52 | import javafx.beans.property.SimpleBooleanProperty; | |
| 53 | import javafx.beans.value.ObservableBooleanValue; | |
| 54 | import javafx.beans.value.ObservableValue; | |
| 55 | import javafx.collections.ListChangeListener.Change; | |
| 56 | import javafx.collections.ObservableList; | |
| 57 | import javafx.event.Event; | |
| 58 | import javafx.event.EventHandler; | |
| 59 | import javafx.geometry.Pos; | |
| 60 | import javafx.scene.Node; | |
| 61 | import javafx.scene.Scene; | |
| 62 | import javafx.scene.control.*; | |
| 63 | import javafx.scene.control.Alert.AlertType; | |
| 64 | import javafx.scene.image.Image; | |
| 65 | import javafx.scene.image.ImageView; | |
| 66 | import javafx.scene.input.KeyEvent; | |
| 67 | import javafx.scene.layout.BorderPane; | |
| 68 | import javafx.scene.layout.VBox; | |
| 69 | import javafx.scene.text.Text; | |
| 70 | import javafx.stage.Window; | |
| 71 | import javafx.stage.WindowEvent; | |
| 72 | import javafx.util.Duration; | |
| 73 | import org.controlsfx.control.StatusBar; | |
| 74 | import org.fxmisc.richtext.model.TwoDimensional.Position; | |
| 75 | ||
| 76 | import java.io.File; | |
| 77 | import java.nio.file.Path; | |
| 78 | import java.util.HashMap; | |
| 79 | import java.util.Map; | |
| 80 | import java.util.Observable; | |
| 81 | import java.util.Observer; | |
| 82 | import java.util.concurrent.atomic.AtomicInteger; | |
| 83 | import java.util.function.Consumer; | |
| 84 | import java.util.function.Function; | |
| 85 | import java.util.prefs.Preferences; | |
| 86 | ||
| 87 | import static com.scrivenvar.Constants.*; | |
| 88 | import static com.scrivenvar.Messages.get; | |
| 89 | import static com.scrivenvar.util.StageState.*; | |
| 90 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*; | |
| 91 | import static javafx.event.Event.fireEvent; | |
| 92 | import static javafx.scene.input.KeyCode.ENTER; | |
| 93 | import static javafx.scene.input.KeyCode.TAB; | |
| 94 | import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST; | |
| 95 | ||
| 96 | /** | |
| 97 | * Main window containing a tab pane in the center for file editors. | |
| 98 | * | |
| 99 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 100 | */ | |
| 101 | public class MainWindow implements Observer { | |
| 102 | ||
| 103 | /** | |
| 104 | * The {@code OPTIONS} variable must be declared before all other variables | |
| 105 | * to prevent subsequent initializations from failing due to missing user | |
| 106 | * preferences. | |
| 107 | */ | |
| 108 | private final static Options OPTIONS = Services.load( Options.class ); | |
| 109 | private final static Snitch SNITCH = Services.load( Snitch.class ); | |
| 110 | private final static Notifier NOTIFIER = Services.load( Notifier.class ); | |
| 111 | ||
| 112 | private final Scene mScene; | |
| 113 | private final StatusBar mStatusBar; | |
| 114 | private final Text mLineNumberText; | |
| 115 | private final TextField mFindTextField; | |
| 116 | ||
| 117 | private DefinitionSource mDefinitionSource = createDefaultDefinitionSource(); | |
| 118 | private final DefinitionPane mDefinitionPane = new DefinitionPane(); | |
| 119 | private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane(); | |
| 120 | private FileEditorTabPane fileEditorPane; | |
| 121 | ||
| 122 | /** | |
| 123 | * Prevents re-instantiation of processing classes. | |
| 124 | */ | |
| 125 | private final Map<FileEditorTab, Processor<String>> mProcessors = | |
| 126 | new HashMap<>(); | |
| 127 | ||
| 128 | private final Map<String, String> mResolvedMap = | |
| 129 | new HashMap<>( DEFAULT_MAP_SIZE ); | |
| 130 | ||
| 131 | /** | |
| 132 | * Listens on the definition pane for double-click events. | |
| 133 | */ | |
| 134 | private VariableNameInjector variableNameInjector; | |
| 135 | ||
| 136 | /** | |
| 137 | * Called when the definition data is changed. | |
| 138 | */ | |
| 139 | private final EventHandler<TreeItem.TreeModificationEvent<Event>> | |
| 140 | mTreeHandler = event -> { | |
| 141 | exportDefinitions( getDefinitionPath() ); | |
| 142 | interpolateResolvedMap(); | |
| 143 | refreshActiveTab(); | |
| 144 | }; | |
| 145 | ||
| 146 | /** | |
| 147 | * Called to inject the selected item when the user presses ENTER in the | |
| 148 | * definition pane. | |
| 149 | */ | |
| 150 | private final EventHandler<? super KeyEvent> mDefinitionKeyHandler = | |
| 151 | event -> { | |
| 152 | if( event.getCode() == ENTER ) { | |
| 153 | getVariableNameInjector().injectSelectedItem(); | |
| 154 | } | |
| 155 | }; | |
| 156 | ||
| 157 | /** | |
| 158 | * Called to switch to the definition pane when the user presses TAB. | |
| 159 | */ | |
| 160 | private final EventHandler<? super KeyEvent> mEditorKeyHandler = | |
| 161 | (EventHandler<KeyEvent>) event -> { | |
| 162 | if( event.getCode() == TAB ) { | |
| 163 | getDefinitionPane().requestFocus(); | |
| 164 | event.consume(); | |
| 165 | } | |
| 166 | }; | |
| 167 | ||
| 168 | private final Object mMutex = new Object(); | |
| 169 | private final AtomicInteger mScrollRatio = new AtomicInteger( 0 ); | |
| 170 | ||
| 171 | /** | |
| 172 | * Called to synchronize the scrolling areas. | |
| 173 | */ | |
| 174 | private final Consumer<Double> mScrollEventObserver = o -> { | |
| 175 | final var eScrollPane = getActiveEditor().getScrollPane(); | |
| 176 | final int eScrollY = | |
| 177 | eScrollPane.estimatedScrollYProperty().getValue().intValue(); | |
| 178 | final int eHeight = (int) | |
| 179 | (eScrollPane.totalHeightEstimateProperty().getValue().intValue() | |
| 180 | - eScrollPane.getHeight()); | |
| 181 | final double eRatio = eHeight > 0 | |
| 182 | ? Math.min( Math.max( eScrollY / (float) eHeight, 0 ), 1 ) : 0; | |
| 183 | ||
| 184 | final var pPreviewPane = getPreviewPane(); | |
| 185 | final var pScrollBar = pPreviewPane.getVerticalScrollBar(); | |
| 186 | final var pHeight = pScrollBar.getMaximum() - pScrollBar.getHeight(); | |
| 187 | final var pScrollY = (int) (pHeight * eRatio); | |
| 188 | final var pScrollPane = pPreviewPane.getScrollPane(); | |
| 189 | ||
| 190 | final int oldScrollY = mScrollRatio.getAndSet( pScrollY ); | |
| 191 | final int delta = Math.abs( oldScrollY - pScrollY ); | |
| 192 | ||
| 193 | if( delta > 33 ) { | |
| 194 | // Prevent concurrent modification exceptions when attempting to | |
| 195 | // set the vertical scroll bar position. | |
| 196 | synchronized( mMutex ) { | |
| 197 | Platform.runLater( () -> { | |
| 198 | pScrollBar.setValue( pScrollY ); | |
| 199 | pScrollPane.repaint(); | |
| 200 | } ); | |
| 201 | } | |
| 202 | } | |
| 203 | }; | |
| 204 | ||
| 205 | public MainWindow() { | |
| 206 | mStatusBar = createStatusBar(); | |
| 207 | mLineNumberText = createLineNumberText(); | |
| 208 | mFindTextField = createFindTextField(); | |
| 209 | mScene = createScene(); | |
| 210 | ||
| 211 | initLayout(); | |
| 212 | initFindInput(); | |
| 213 | initSnitch(); | |
| 214 | initDefinitionListener(); | |
| 215 | initTabAddedListener(); | |
| 216 | initTabChangedListener(); | |
| 217 | restorePreferences(); | |
| 218 | } | |
| 219 | ||
| 220 | private void initLayout() { | |
| 221 | final Scene appScene = getScene(); | |
| 222 | ||
| 223 | appScene.getStylesheets().add( STYLESHEET_SCENE ); | |
| 224 | ||
| 225 | // TODO: Apply an XML syntax highlighting for XML files. | |
| 226 | // appScene.getStylesheets().add( STYLESHEET_XML ); | |
| 227 | appScene.windowProperty().addListener( | |
| 228 | ( observable, oldWindow, newWindow ) -> | |
| 229 | newWindow.setOnCloseRequest( | |
| 230 | e -> { | |
| 231 | if( !getFileEditorPane().closeAllEditors() ) { | |
| 232 | e.consume(); | |
| 233 | } | |
| 234 | } | |
| 235 | ) | |
| 236 | ); | |
| 237 | } | |
| 238 | ||
| 239 | /** | |
| 240 | * Initialize the find input text field to listen on F3, ENTER, and ESCAPE key | |
| 241 | * presses. | |
| 242 | */ | |
| 243 | private void initFindInput() { | |
| 244 | final TextField input = getFindTextField(); | |
| 245 | ||
| 246 | input.setOnKeyPressed( ( KeyEvent event ) -> { | |
| 247 | switch( event.getCode() ) { | |
| 248 | case F3: | |
| 249 | case ENTER: | |
| 250 | editFindNext(); | |
| 251 | break; | |
| 252 | case F: | |
| 253 | if( !event.isControlDown() ) { | |
| 254 | break; | |
| 255 | } | |
| 256 | case ESCAPE: | |
| 257 | getStatusBar().setGraphic( null ); | |
| 258 | getActiveFileEditor().getEditorPane().requestFocus(); | |
| 259 | break; | |
| 260 | } | |
| 261 | } ); | |
| 262 | ||
| 263 | // Remove when the input field loses focus. | |
| 264 | input.focusedProperty().addListener( | |
| 265 | ( | |
| 266 | final ObservableValue<? extends Boolean> focused, | |
| 267 | final Boolean oFocus, | |
| 268 | final Boolean nFocus ) -> { | |
| 269 | if( !nFocus ) { | |
| 270 | getStatusBar().setGraphic( null ); | |
| 271 | } | |
| 272 | } | |
| 273 | ); | |
| 274 | } | |
| 275 | ||
| 276 | /** | |
| 277 | * Watch for changes to external files. In particular, this awaits | |
| 278 | * modifications to any XSL files associated with XML files being edited. When | |
| 279 | * an XSL file is modified (external to the application), the snitch's ears | |
| 280 | * perk up and the file is reloaded. This keeps the XSL transformation up to | |
| 281 | * date with what's on the file system. | |
| 282 | */ | |
| 283 | private void initSnitch() { | |
| 284 | SNITCH.addObserver( this ); | |
| 285 | } | |
| 286 | ||
| 287 | /** | |
| 288 | * Listen for {@link FileEditorTabPane} to receive open definition file event. | |
| 289 | */ | |
| 290 | private void initDefinitionListener() { | |
| 291 | getFileEditorPane().onOpenDefinitionFileProperty().addListener( | |
| 292 | ( final ObservableValue<? extends Path> file, | |
| 293 | final Path oldPath, final Path newPath ) -> { | |
| 294 | // Indirectly refresh the resolved map. | |
| 295 | resetProcessors(); | |
| 296 | ||
| 297 | openDefinitions( newPath ); | |
| 298 | ||
| 299 | // Will create new processors and therefore a new resolved map. | |
| 300 | refreshActiveTab(); | |
| 301 | } | |
| 302 | ); | |
| 303 | } | |
| 304 | ||
| 305 | /** | |
| 306 | * When tabs are added, hook the various change listeners onto the new tab so | |
| 307 | * that the preview pane refreshes as necessary. | |
| 308 | */ | |
| 309 | private void initTabAddedListener() { | |
| 310 | final FileEditorTabPane editorPane = getFileEditorPane(); | |
| 311 | ||
| 312 | // Make sure the text processor kicks off when new files are opened. | |
| 313 | final ObservableList<Tab> tabs = editorPane.getTabs(); | |
| 314 | ||
| 315 | // Update the preview pane on tab changes. | |
| 316 | tabs.addListener( | |
| 317 | ( final Change<? extends Tab> change ) -> { | |
| 318 | while( change.next() ) { | |
| 319 | if( change.wasAdded() ) { | |
| 320 | // Multiple tabs can be added simultaneously. | |
| 321 | for( final Tab newTab : change.getAddedSubList() ) { | |
| 322 | final FileEditorTab tab = (FileEditorTab) newTab; | |
| 323 | ||
| 324 | initTextChangeListener( tab ); | |
| 325 | initKeyboardEventListeners( tab ); | |
| 326 | // initSyntaxListener( tab ); | |
| 327 | } | |
| 328 | } | |
| 329 | } | |
| 330 | } | |
| 331 | ); | |
| 332 | } | |
| 333 | ||
| 334 | /** | |
| 335 | * Listen for new tab selection events. | |
| 336 | */ | |
| 337 | private void initTabChangedListener() { | |
| 338 | final FileEditorTabPane editorPane = getFileEditorPane(); | |
| 339 | ||
| 340 | // Update the preview pane changing tabs. | |
| 341 | editorPane.addTabSelectionListener( | |
| 342 | ( ObservableValue<? extends Tab> tabPane, | |
| 343 | final Tab oldTab, final Tab newTab ) -> { | |
| 344 | updateVariableNameInjector(); | |
| 345 | ||
| 346 | // If there was no old tab, then this is a first time load, which | |
| 347 | // can be ignored. | |
| 348 | if( oldTab != null ) { | |
| 349 | if( newTab == null ) { | |
| 350 | closeRemainingTab(); | |
| 351 | } | |
| 352 | else { | |
| 353 | // Update the preview with the edited text. | |
| 354 | refreshSelectedTab( (FileEditorTab) newTab ); | |
| 355 | } | |
| 356 | } | |
| 357 | } | |
| 358 | ); | |
| 359 | } | |
| 360 | ||
| 361 | /** | |
| 362 | * Reloads the preferences from the previous session. | |
| 363 | */ | |
| 364 | private void restorePreferences() { | |
| 365 | restoreDefinitionPane(); | |
| 366 | getFileEditorPane().restorePreferences(); | |
| 367 | } | |
| 368 | ||
| 369 | /** | |
| 370 | * Ensure that the keyboard events are received when a new tab is added | |
| 371 | * to the user interface. | |
| 372 | * | |
| 373 | * @param tab The tab that can trigger keyboard events, such as control+space. | |
| 374 | */ | |
| 375 | private void initKeyboardEventListeners( final FileEditorTab tab ) { | |
| 376 | final VariableNameInjector vin = getVariableNameInjector(); | |
| 377 | vin.initKeyboardEventListeners( tab ); | |
| 378 | ||
| 379 | tab.addEventFilter( KeyEvent.KEY_PRESSED, mEditorKeyHandler ); | |
| 380 | } | |
| 381 | ||
| 382 | private void initTextChangeListener( final FileEditorTab tab ) { | |
| 383 | tab.addTextChangeListener( | |
| 384 | ( ObservableValue<? extends String> editor, | |
| 385 | final String oldValue, final String newValue ) -> | |
| 386 | refreshSelectedTab( tab ) | |
| 387 | ); | |
| 388 | } | |
| 389 | ||
| 390 | private void updateVariableNameInjector() { | |
| 391 | getVariableNameInjector().setFileEditorTab( getActiveFileEditor() ); | |
| 392 | } | |
| 393 | ||
| 394 | private void setVariableNameInjector( final VariableNameInjector injector ) { | |
| 395 | this.variableNameInjector = injector; | |
| 396 | } | |
| 397 | ||
| 398 | private synchronized VariableNameInjector getVariableNameInjector() { | |
| 399 | if( this.variableNameInjector == null ) { | |
| 400 | final VariableNameInjector vin = createVariableNameInjector(); | |
| 401 | setVariableNameInjector( vin ); | |
| 402 | } | |
| 403 | ||
| 404 | return this.variableNameInjector; | |
| 405 | } | |
| 406 | ||
| 407 | private VariableNameInjector createVariableNameInjector() { | |
| 408 | final FileEditorTab tab = getActiveFileEditor(); | |
| 409 | final DefinitionPane pane = getDefinitionPane(); | |
| 410 | ||
| 411 | return new VariableNameInjector( tab, pane ); | |
| 412 | } | |
| 413 | ||
| 414 | /** | |
| 415 | * Called whenever the preview pane becomes out of sync with the file editor | |
| 416 | * tab. This can be called when the text changes, the caret paragraph changes, | |
| 417 | * or the file tab changes. | |
| 418 | * | |
| 419 | * @param tab The file editor tab that has been changed in some fashion. | |
| 420 | */ | |
| 421 | private void refreshSelectedTab( final FileEditorTab tab ) { | |
| 422 | if( tab == null ) { | |
| 423 | return; | |
| 424 | } | |
| 425 | ||
| 426 | getPreviewPane().setPath( tab.getPath() ); | |
| 427 | ||
| 428 | // TODO: https://github.com/DaveJarvis/scrivenvar/issues/29 | |
| 429 | final Position p = tab.getCaretOffset(); | |
| 430 | getLineNumberText().setText( | |
| 431 | get( STATUS_BAR_LINE, | |
| 432 | p.getMajor() + 1, | |
| 433 | p.getMinor() + 1, | |
| 434 | tab.getCaretPosition() + 1 | |
| 435 | ) | |
| 436 | ); | |
| 437 | ||
| 438 | Processor<String> processor = getProcessors().get( tab ); | |
| 439 | ||
| 440 | if( processor == null ) { | |
| 441 | processor = createProcessor( tab ); | |
| 442 | getProcessors().put( tab, processor ); | |
| 443 | } | |
| 444 | ||
| 445 | try { | |
| 446 | processor.processChain( tab.getEditorText() ); | |
| 447 | } catch( final Exception ex ) { | |
| 448 | error( ex ); | |
| 449 | } | |
| 450 | } | |
| 451 | ||
| 452 | private void refreshActiveTab() { | |
| 453 | refreshSelectedTab( getActiveFileEditor() ); | |
| 454 | } | |
| 455 | ||
| 456 | /** | |
| 457 | * Called when a definition source is opened. | |
| 458 | * | |
| 459 | * @param path Path to the definition source that was opened. | |
| 460 | */ | |
| 461 | private void openDefinitions( final Path path ) { | |
| 462 | try { | |
| 463 | final DefinitionSource ds = createDefinitionSource( path ); | |
| 464 | setDefinitionSource( ds ); | |
| 465 | getUserPreferences().definitionPathProperty().setValue( path.toFile() ); | |
| 466 | getUserPreferences().save(); | |
| 467 | ||
| 468 | final Tooltip tooltipPath = new Tooltip( path.toString() ); | |
| 469 | tooltipPath.setShowDelay( Duration.millis( 200 ) ); | |
| 470 | ||
| 471 | final DefinitionPane pane = getDefinitionPane(); | |
| 472 | pane.update( ds ); | |
| 473 | pane.addTreeChangeHandler( mTreeHandler ); | |
| 474 | pane.addKeyEventHandler( mDefinitionKeyHandler ); | |
| 475 | pane.filenameProperty().setValue( path.getFileName().toString() ); | |
| 476 | pane.setTooltip( tooltipPath ); | |
| 477 | ||
| 478 | interpolateResolvedMap(); | |
| 479 | } catch( final Exception e ) { | |
| 480 | error( e ); | |
| 481 | } | |
| 482 | } | |
| 483 | ||
| 484 | private void exportDefinitions( final Path path ) { | |
| 485 | try { | |
| 486 | final DefinitionPane pane = getDefinitionPane(); | |
| 487 | final TreeItem<String> root = pane.getTreeView().getRoot(); | |
| 488 | final TreeItem<String> problemChild = pane.isTreeWellFormed(); | |
| 489 | ||
| 490 | if( problemChild == null ) { | |
| 491 | getDefinitionSource().getTreeAdapter().export( root, path ); | |
| 492 | getNotifier().clear(); | |
| 493 | } | |
| 494 | else { | |
| 495 | final String msg = get( "yaml.error.tree.form", | |
| 496 | problemChild.getValue() ); | |
| 497 | getNotifier().notify( msg ); | |
| 498 | } | |
| 499 | } catch( final Exception e ) { | |
| 500 | error( e ); | |
| 501 | } | |
| 502 | } | |
| 503 | ||
| 504 | private void interpolateResolvedMap() { | |
| 505 | final Map<String, String> treeMap = getDefinitionPane().toMap(); | |
| 506 | final Map<String, String> map = new HashMap<>( treeMap ); | |
| 507 | MapInterpolator.interpolate( map ); | |
| 508 | ||
| 509 | getResolvedMap().clear(); | |
| 510 | getResolvedMap().putAll( map ); | |
| 511 | } | |
| 512 | ||
| 513 | private void restoreDefinitionPane() { | |
| 514 | openDefinitions( getDefinitionPath() ); | |
| 515 | } | |
| 516 | ||
| 517 | /** | |
| 518 | * Called when the last open tab is closed to clear the preview pane. | |
| 519 | */ | |
| 520 | private void closeRemainingTab() { | |
| 521 | getPreviewPane().clear(); | |
| 522 | } | |
| 523 | ||
| 524 | /** | |
| 525 | * Called when an exception occurs that warrants the user's attention. | |
| 526 | * | |
| 527 | * @param e The exception with a message that the user should know about. | |
| 528 | */ | |
| 529 | private void error( final Exception e ) { | |
| 530 | getNotifier().notify( e ); | |
| 531 | } | |
| 532 | ||
| 533 | //---- File actions ------------------------------------------------------- | |
| 534 | ||
| 535 | /** | |
| 536 | * Called when an observable instance has changed. This is called by both the | |
| 537 | * snitch service and the notify service. The snitch service can be called for | |
| 538 | * different file types, including definition sources. | |
| 539 | * | |
| 540 | * @param observable The observed instance. | |
| 541 | * @param value The noteworthy item. | |
| 542 | */ | |
| 543 | @Override | |
| 544 | public void update( final Observable observable, final Object value ) { | |
| 545 | if( value != null ) { | |
| 546 | if( observable instanceof Snitch && value instanceof Path ) { | |
| 547 | updateSelectedTab(); | |
| 548 | } | |
| 549 | else if( observable instanceof Notifier && value instanceof String ) { | |
| 550 | updateStatusBar( (String) value ); | |
| 551 | } | |
| 552 | } | |
| 553 | } | |
| 554 | ||
| 555 | /** | |
| 556 | * Updates the status bar to show the given message. | |
| 557 | * | |
| 558 | * @param s The message to show in the status bar. | |
| 559 | */ | |
| 560 | private void updateStatusBar( final String s ) { | |
| 561 | Platform.runLater( | |
| 562 | () -> { | |
| 563 | final int index = s.indexOf( '\n' ); | |
| 564 | final String message = s.substring( | |
| 565 | 0, index > 0 ? index : s.length() ); | |
| 566 | ||
| 567 | getStatusBar().setText( message ); | |
| 568 | } | |
| 569 | ); | |
| 570 | } | |
| 571 | ||
| 572 | /** | |
| 573 | * Called when a file has been modified. | |
| 574 | */ | |
| 575 | private void updateSelectedTab() { | |
| 576 | Platform.runLater( | |
| 577 | () -> { | |
| 578 | // Brute-force XSLT file reload by re-instantiating all processors. | |
| 579 | resetProcessors(); | |
| 580 | refreshActiveTab(); | |
| 581 | } | |
| 582 | ); | |
| 583 | } | |
| 584 | ||
| 585 | /** | |
| 586 | * After resetting the processors, they will refresh anew to be up-to-date | |
| 587 | * with the files (text and definition) currently loaded into the editor. | |
| 588 | */ | |
| 589 | private void resetProcessors() { | |
| 590 | getProcessors().clear(); | |
| 591 | } | |
| 592 | ||
| 593 | //---- File actions ------------------------------------------------------- | |
| 594 | ||
| 595 | private void fileNew() { | |
| 596 | getFileEditorPane().newEditor(); | |
| 597 | } | |
| 598 | ||
| 599 | private void fileOpen() { | |
| 600 | getFileEditorPane().openFileDialog(); | |
| 601 | } | |
| 602 | ||
| 603 | private void fileClose() { | |
| 604 | getFileEditorPane().closeEditor( getActiveFileEditor(), true ); | |
| 605 | } | |
| 606 | ||
| 607 | /** | |
| 608 | * TODO: Upon closing, first remove the tab change listeners. (There's no | |
| 609 | * need to re-render each tab when all are being closed.) | |
| 610 | */ | |
| 611 | private void fileCloseAll() { | |
| 612 | getFileEditorPane().closeAllEditors(); | |
| 613 | } | |
| 614 | ||
| 615 | private void fileSave() { | |
| 616 | getFileEditorPane().saveEditor( getActiveFileEditor() ); | |
| 617 | } | |
| 618 | ||
| 619 | private void fileSaveAs() { | |
| 620 | final FileEditorTab editor = getActiveFileEditor(); | |
| 621 | getFileEditorPane().saveEditorAs( editor ); | |
| 622 | getProcessors().remove( editor ); | |
| 623 | ||
| 624 | try { | |
| 625 | refreshSelectedTab( editor ); | |
| 626 | } catch( final Exception ex ) { | |
| 627 | getNotifier().notify( ex ); | |
| 628 | } | |
| 629 | } | |
| 630 | ||
| 631 | private void fileSaveAll() { | |
| 632 | getFileEditorPane().saveAllEditors(); | |
| 633 | } | |
| 634 | ||
| 635 | private void fileExit() { | |
| 636 | final Window window = getWindow(); | |
| 637 | fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); | |
| 638 | } | |
| 639 | ||
| 640 | //---- Edit actions ------------------------------------------------------- | |
| 641 | ||
| 642 | /** | |
| 643 | * Used to find text in the active file editor window. | |
| 644 | */ | |
| 645 | private void editFind() { | |
| 646 | final TextField input = getFindTextField(); | |
| 647 | getStatusBar().setGraphic( input ); | |
| 648 | input.requestFocus(); | |
| 649 | } | |
| 650 | ||
| 651 | public void editFindNext() { | |
| 652 | getActiveFileEditor().searchNext( getFindTextField().getText() ); | |
| 653 | } | |
| 654 | ||
| 655 | public void editPreferences() { | |
| 656 | getUserPreferences().show(); | |
| 657 | } | |
| 658 | ||
| 659 | //---- Insert actions ----------------------------------------------------- | |
| 660 | ||
| 661 | /** | |
| 662 | * Delegates to the active editor to handle wrapping the current text | |
| 663 | * selection with leading and trailing strings. | |
| 664 | * | |
| 665 | * @param leading The string to put before the selection. | |
| 666 | * @param trailing The string to put after the selection. | |
| 667 | */ | |
| 668 | private void insertMarkdown( | |
| 669 | final String leading, final String trailing ) { | |
| 670 | getActiveEditor().surroundSelection( leading, trailing ); | |
| 671 | } | |
| 672 | ||
| 673 | @SuppressWarnings("SameParameterValue") | |
| 674 | private void insertMarkdown( | |
| 675 | final String leading, final String trailing, final String hint ) { | |
| 676 | getActiveEditor().surroundSelection( leading, trailing, hint ); | |
| 677 | } | |
| 678 | ||
| 679 | //---- Help actions ------------------------------------------------------- | |
| 680 | ||
| 681 | private void helpAbout() { | |
| 682 | final Alert alert = new Alert( AlertType.INFORMATION ); | |
| 683 | alert.setTitle( get( "Dialog.about.title" ) ); | |
| 684 | alert.setHeaderText( get( "Dialog.about.header" ) ); | |
| 685 | alert.setContentText( get( "Dialog.about.content" ) ); | |
| 686 | alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) ); | |
| 687 | alert.initOwner( getWindow() ); | |
| 688 | ||
| 689 | alert.showAndWait(); | |
| 690 | } | |
| 691 | ||
| 692 | //---- Member creators ---------------------------------------------------- | |
| 693 | ||
| 694 | /** | |
| 695 | * Factory to create processors that are suited to different file types. | |
| 696 | * | |
| 697 | * @param tab The tab that is subjected to processing. | |
| 698 | * @return A processor suited to the file type specified by the tab's path. | |
| 699 | */ | |
| 700 | private Processor<String> createProcessor( final FileEditorTab tab ) { | |
| 701 | return createProcessorFactory().createProcessor( tab ); | |
| 702 | } | |
| 703 | ||
| 704 | private ProcessorFactory createProcessorFactory() { | |
| 705 | return new ProcessorFactory( getPreviewPane(), getResolvedMap() ); | |
| 706 | } | |
| 707 | ||
| 708 | private HTMLPreviewPane createHTMLPreviewPane() { | |
| 709 | return new HTMLPreviewPane(); | |
| 710 | } | |
| 711 | ||
| 712 | private DefinitionSource createDefaultDefinitionSource() { | |
| 713 | return new YamlDefinitionSource( getDefinitionPath() ); | |
| 714 | } | |
| 715 | ||
| 716 | private DefinitionSource createDefinitionSource( final Path path ) { | |
| 717 | try { | |
| 718 | return createDefinitionFactory().createDefinitionSource( path ); | |
| 719 | } catch( final Exception ex ) { | |
| 720 | error( ex ); | |
| 721 | return createDefaultDefinitionSource(); | |
| 722 | } | |
| 723 | } | |
| 724 | ||
| 725 | private TextField createFindTextField() { | |
| 726 | return new TextField(); | |
| 727 | } | |
| 728 | ||
| 729 | /** | |
| 730 | * Create an editor pane to hold file editor tabs. | |
| 731 | * | |
| 732 | * @return A new instance, never null. | |
| 733 | */ | |
| 734 | private FileEditorTabPane createFileEditorPane() { | |
| 735 | return new FileEditorTabPane( mScrollEventObserver ); | |
| 736 | } | |
| 737 | ||
| 738 | private DefinitionFactory createDefinitionFactory() { | |
| 739 | return new DefinitionFactory(); | |
| 740 | } | |
| 741 | ||
| 742 | private StatusBar createStatusBar() { | |
| 743 | return new StatusBar(); | |
| 744 | } | |
| 745 | ||
| 746 | private Scene createScene() { | |
| 747 | final SplitPane splitPane = new SplitPane( | |
| 748 | getDefinitionPane().getNode(), | |
| 749 | getFileEditorPane().getNode(), | |
| 750 | getPreviewPane().getNode() ); | |
| 751 | ||
| 752 | splitPane.setDividerPositions( | |
| 753 | getFloat( K_PANE_SPLIT_DEFINITION, .10f ), | |
| 754 | getFloat( K_PANE_SPLIT_EDITOR, .45f ), | |
| 755 | getFloat( K_PANE_SPLIT_PREVIEW, .45f ) ); | |
| 756 | ||
| 757 | getDefinitionPane().prefHeightProperty().bind( splitPane.heightProperty() ); | |
| 758 | ||
| 759 | final BorderPane borderPane = new BorderPane(); | |
| 760 | borderPane.setPrefSize( 1024, 800 ); | |
| 761 | borderPane.setTop( createMenuBar() ); | |
| 762 | borderPane.setBottom( getStatusBar() ); | |
| 763 | borderPane.setCenter( splitPane ); | |
| 764 | ||
| 765 | final VBox statusBar = new VBox(); | |
| 766 | statusBar.setAlignment( Pos.BASELINE_CENTER ); | |
| 767 | statusBar.getChildren().add( getLineNumberText() ); | |
| 768 | getStatusBar().getRightItems().add( statusBar ); | |
| 769 | ||
| 770 | return new Scene( borderPane ); | |
| 771 | } | |
| 772 | ||
| 773 | private Text createLineNumberText() { | |
| 774 | return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) ); | |
| 775 | } | |
| 776 | ||
| 777 | private Node createMenuBar() { | |
| 778 | final BooleanBinding activeFileEditorIsNull = | |
| 779 | getFileEditorPane().activeFileEditorProperty().isNull(); | |
| 780 | ||
| 781 | // File actions | |
| 782 | final Action fileNewAction = new ActionBuilder() | |
| 783 | .setText( "Main.menu.file.new" ) | |
| 784 | .setAccelerator( "Shortcut+N" ) | |
| 785 | .setIcon( FILE_ALT ) | |
| 786 | .setAction( e -> fileNew() ) | |
| 787 | .build(); | |
| 788 | final Action fileOpenAction = new ActionBuilder() | |
| 789 | .setText( "Main.menu.file.open" ) | |
| 790 | .setAccelerator( "Shortcut+O" ) | |
| 791 | .setIcon( FOLDER_OPEN_ALT ) | |
| 792 | .setAction( e -> fileOpen() ) | |
| 793 | .build(); | |
| 794 | final Action fileCloseAction = new ActionBuilder() | |
| 795 | .setText( "Main.menu.file.close" ) | |
| 796 | .setAccelerator( "Shortcut+W" ) | |
| 797 | .setAction( e -> fileClose() ) | |
| 798 | .setDisable( activeFileEditorIsNull ) | |
| 799 | .build(); | |
| 800 | final Action fileCloseAllAction = new ActionBuilder() | |
| 801 | .setText( "Main.menu.file.close_all" ) | |
| 802 | .setAction( e -> fileCloseAll() ) | |
| 803 | .setDisable( activeFileEditorIsNull ) | |
| 804 | .build(); | |
| 805 | final Action fileSaveAction = new ActionBuilder() | |
| 806 | .setText( "Main.menu.file.save" ) | |
| 807 | .setAccelerator( "Shortcut+S" ) | |
| 808 | .setIcon( FLOPPY_ALT ) | |
| 809 | .setAction( e -> fileSave() ) | |
| 810 | .setDisable( createActiveBooleanProperty( | |
| 811 | FileEditorTab::modifiedProperty ).not() ) | |
| 812 | .build(); | |
| 813 | final Action fileSaveAsAction = new ActionBuilder() | |
| 814 | .setText( "Main.menu.file.save_as" ) | |
| 815 | .setAction( e -> fileSaveAs() ) | |
| 816 | .setDisable( activeFileEditorIsNull ) | |
| 817 | .build(); | |
| 818 | final Action fileSaveAllAction = new ActionBuilder() | |
| 819 | .setText( "Main.menu.file.save_all" ) | |
| 820 | .setAccelerator( "Shortcut+Shift+S" ) | |
| 821 | .setAction( e -> fileSaveAll() ) | |
| 822 | .setDisable( Bindings.not( | |
| 823 | getFileEditorPane().anyFileEditorModifiedProperty() ) ) | |
| 824 | .build(); | |
| 825 | final Action fileExitAction = new ActionBuilder() | |
| 826 | .setText( "Main.menu.file.exit" ) | |
| 827 | .setAction( e -> fileExit() ) | |
| 828 | .build(); | |
| 829 | ||
| 830 | // Edit actions | |
| 831 | final Action editUndoAction = new ActionBuilder() | |
| 832 | .setText( "Main.menu.edit.undo" ) | |
| 833 | .setAccelerator( "Shortcut+Z" ) | |
| 834 | .setIcon( UNDO ) | |
| 835 | .setAction( e -> getActiveEditor().undo() ) | |
| 836 | .setDisable( createActiveBooleanProperty( | |
| 837 | FileEditorTab::canUndoProperty ).not() ) | |
| 838 | .build(); | |
| 839 | final Action editRedoAction = new ActionBuilder() | |
| 840 | .setText( "Main.menu.edit.redo" ) | |
| 841 | .setAccelerator( "Shortcut+Y" ) | |
| 842 | .setIcon( REPEAT ) | |
| 843 | .setAction( e -> getActiveEditor().redo() ) | |
| 844 | .setDisable( createActiveBooleanProperty( | |
| 845 | FileEditorTab::canRedoProperty ).not() ) | |
| 846 | .build(); | |
| 847 | final Action editFindAction = new ActionBuilder() | |
| 848 | .setText( "Main.menu.edit.find" ) | |
| 849 | .setAccelerator( "Ctrl+F" ) | |
| 850 | .setIcon( SEARCH ) | |
| 851 | .setAction( e -> editFind() ) | |
| 852 | .setDisable( activeFileEditorIsNull ) | |
| 853 | .build(); | |
| 854 | final Action editFindNextAction = new ActionBuilder() | |
| 855 | .setText( "Main.menu.edit.find.next" ) | |
| 856 | .setAccelerator( "F3" ) | |
| 857 | .setIcon( null ) | |
| 858 | .setAction( e -> editFindNext() ) | |
| 859 | .setDisable( activeFileEditorIsNull ) | |
| 860 | .build(); | |
| 861 | final Action editPreferencesAction = new ActionBuilder() | |
| 862 | .setText( "Main.menu.edit.preferences" ) | |
| 863 | .setAccelerator( "Ctrl+Alt+S" ) | |
| 864 | .setAction( e -> editPreferences() ) | |
| 865 | .build(); | |
| 866 | ||
| 867 | // Insert actions | |
| 868 | final Action insertBoldAction = new ActionBuilder() | |
| 869 | .setText( "Main.menu.insert.bold" ) | |
| 870 | .setAccelerator( "Shortcut+B" ) | |
| 871 | .setIcon( BOLD ) | |
| 872 | .setAction( e -> insertMarkdown( "**", "**" ) ) | |
| 873 | .setDisable( activeFileEditorIsNull ) | |
| 874 | .build(); | |
| 875 | final Action insertItalicAction = new ActionBuilder() | |
| 876 | .setText( "Main.menu.insert.italic" ) | |
| 877 | .setAccelerator( "Shortcut+I" ) | |
| 878 | .setIcon( ITALIC ) | |
| 879 | .setAction( e -> insertMarkdown( "*", "*" ) ) | |
| 880 | .setDisable( activeFileEditorIsNull ) | |
| 881 | .build(); | |
| 882 | final Action insertSuperscriptAction = new ActionBuilder() | |
| 883 | .setText( "Main.menu.insert.superscript" ) | |
| 884 | .setAccelerator( "Shortcut+[" ) | |
| 885 | .setIcon( SUPERSCRIPT ) | |
| 886 | .setAction( e -> insertMarkdown( "^", "^" ) ) | |
| 887 | .setDisable( activeFileEditorIsNull ) | |
| 888 | .build(); | |
| 889 | final Action insertSubscriptAction = new ActionBuilder() | |
| 890 | .setText( "Main.menu.insert.subscript" ) | |
| 891 | .setAccelerator( "Shortcut+]" ) | |
| 892 | .setIcon( SUBSCRIPT ) | |
| 893 | .setAction( e -> insertMarkdown( "~", "~" ) ) | |
| 894 | .setDisable( activeFileEditorIsNull ) | |
| 895 | .build(); | |
| 896 | final Action insertStrikethroughAction = new ActionBuilder() | |
| 897 | .setText( "Main.menu.insert.strikethrough" ) | |
| 898 | .setAccelerator( "Shortcut+T" ) | |
| 899 | .setIcon( STRIKETHROUGH ) | |
| 900 | .setAction( e -> insertMarkdown( "~~", "~~" ) ) | |
| 901 | .setDisable( activeFileEditorIsNull ) | |
| 902 | .build(); | |
| 903 | final Action insertBlockquoteAction = new ActionBuilder() | |
| 904 | .setText( "Main.menu.insert.blockquote" ) | |
| 905 | .setAccelerator( "Ctrl+Q" ) | |
| 906 | .setIcon( QUOTE_LEFT ) | |
| 907 | .setAction( e -> insertMarkdown( "\n\n> ", "" ) ) | |
| 908 | .setDisable( activeFileEditorIsNull ) | |
| 909 | .build(); | |
| 910 | final Action insertCodeAction = new ActionBuilder() | |
| 911 | .setText( "Main.menu.insert.code" ) | |
| 912 | .setAccelerator( "Shortcut+K" ) | |
| 913 | .setIcon( CODE ) | |
| 914 | .setAction( e -> insertMarkdown( "`", "`" ) ) | |
| 915 | .setDisable( activeFileEditorIsNull ) | |
| 916 | .build(); | |
| 917 | final Action insertFencedCodeBlockAction = new ActionBuilder() | |
| 918 | .setText( "Main.menu.insert.fenced_code_block" ) | |
| 919 | .setAccelerator( "Shortcut+Shift+K" ) | |
| 920 | .setIcon( FILE_CODE_ALT ) | |
| 921 | .setAction( e -> getActiveEditor().surroundSelection( | |
| 922 | "\n\n```\n", | |
| 923 | "\n```\n\n", | |
| 924 | get( "Main.menu.insert.fenced_code_block.prompt" ) ) ) | |
| 925 | .setDisable( activeFileEditorIsNull ) | |
| 926 | .build(); | |
| 927 | final Action insertLinkAction = new ActionBuilder() | |
| 928 | .setText( "Main.menu.insert.link" ) | |
| 929 | .setAccelerator( "Shortcut+L" ) | |
| 930 | .setIcon( LINK ) | |
| 931 | .setAction( e -> getActiveEditor().insertLink() ) | |
| 932 | .setDisable( activeFileEditorIsNull ) | |
| 933 | .build(); | |
| 934 | final Action insertImageAction = new ActionBuilder() | |
| 935 | .setText( "Main.menu.insert.image" ) | |
| 936 | .setAccelerator( "Shortcut+G" ) | |
| 937 | .setIcon( PICTURE_ALT ) | |
| 938 | .setAction( e -> getActiveEditor().insertImage() ) | |
| 939 | .setDisable( activeFileEditorIsNull ) | |
| 940 | .build(); | |
| 941 | ||
| 942 | // Number of header actions (H1 ... H3) | |
| 943 | final int HEADERS = 3; | |
| 944 | final Action[] headers = new Action[ HEADERS ]; | |
| 945 | ||
| 946 | for( int i = 1; i <= HEADERS; i++ ) { | |
| 947 | final String hashes = new String( new char[ i ] ).replace( "\0", "#" ); | |
| 948 | final String markup = String.format( "%n%n%s ", hashes ); | |
| 949 | final String text = "Main.menu.insert.header." + i; | |
| 950 | final String accelerator = "Shortcut+" + i; | |
| 951 | final String prompt = text + ".prompt"; | |
| 952 | ||
| 953 | headers[ i - 1 ] = new ActionBuilder() | |
| 954 | .setText( text ) | |
| 955 | .setAccelerator( accelerator ) | |
| 956 | .setIcon( HEADER ) | |
| 957 | .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) ) | |
| 958 | .setDisable( activeFileEditorIsNull ) | |
| 959 | .build(); | |
| 960 | } | |
| 961 | ||
| 962 | final Action insertUnorderedListAction = new ActionBuilder() | |
| 963 | .setText( "Main.menu.insert.unordered_list" ) | |
| 964 | .setAccelerator( "Shortcut+U" ) | |
| 965 | .setIcon( LIST_UL ) | |
| 966 | .setAction( e -> getActiveEditor() | |
| 967 | .surroundSelection( "\n\n* ", "" ) ) | |
| 968 | .setDisable( activeFileEditorIsNull ) | |
| 969 | .build(); | |
| 970 | final Action insertOrderedListAction = new ActionBuilder() | |
| 971 | .setText( "Main.menu.insert.ordered_list" ) | |
| 972 | .setAccelerator( "Shortcut+Shift+O" ) | |
| 973 | .setIcon( LIST_OL ) | |
| 974 | .setAction( e -> insertMarkdown( | |
| 975 | "\n\n1. ", "" ) ) | |
| 976 | .setDisable( activeFileEditorIsNull ) | |
| 977 | .build(); | |
| 978 | final Action insertHorizontalRuleAction = new ActionBuilder() | |
| 979 | .setText( "Main.menu.insert.horizontal_rule" ) | |
| 980 | .setAccelerator( "Shortcut+H" ) | |
| 981 | .setAction( e -> insertMarkdown( | |
| 982 | "\n\n---\n\n", "" ) ) | |
| 983 | .setDisable( activeFileEditorIsNull ) | |
| 984 | .build(); | |
| 985 | ||
| 986 | // Help actions | |
| 987 | final Action helpAboutAction = new ActionBuilder() | |
| 988 | .setText( "Main.menu.help.about" ) | |
| 989 | .setAction( e -> helpAbout() ) | |
| 990 | .build(); | |
| 991 | ||
| 992 | //---- MenuBar ---- | |
| 993 | final Menu fileMenu = ActionUtils.createMenu( | |
| 994 | get( "Main.menu.file" ), | |
| 995 | fileNewAction, | |
| 996 | fileOpenAction, | |
| 997 | null, | |
| 998 | fileCloseAction, | |
| 999 | fileCloseAllAction, | |
| 1000 | null, | |
| 1001 | fileSaveAction, | |
| 1002 | fileSaveAsAction, | |
| 1003 | fileSaveAllAction, | |
| 1004 | null, | |
| 1005 | fileExitAction ); | |
| 1006 | ||
| 1007 | final Menu editMenu = ActionUtils.createMenu( | |
| 1008 | get( "Main.menu.edit" ), | |
| 1009 | editUndoAction, | |
| 1010 | editRedoAction, | |
| 1011 | editFindAction, | |
| 1012 | editFindNextAction, | |
| 1013 | null, | |
| 1014 | editPreferencesAction ); | |
| 1015 | ||
| 1016 | final Menu insertMenu = ActionUtils.createMenu( | |
| 1017 | get( "Main.menu.insert" ), | |
| 1018 | insertBoldAction, | |
| 1019 | insertItalicAction, | |
| 1020 | insertSuperscriptAction, | |
| 1021 | insertSubscriptAction, | |
| 1022 | insertStrikethroughAction, | |
| 1023 | insertBlockquoteAction, | |
| 1024 | insertCodeAction, | |
| 1025 | insertFencedCodeBlockAction, | |
| 1026 | null, | |
| 1027 | insertLinkAction, | |
| 1028 | insertImageAction, | |
| 1029 | null, | |
| 1030 | headers[ 0 ], | |
| 1031 | headers[ 1 ], | |
| 1032 | headers[ 2 ], | |
| 1033 | null, | |
| 1034 | insertUnorderedListAction, | |
| 1035 | insertOrderedListAction, | |
| 1036 | insertHorizontalRuleAction ); | |
| 1037 | ||
| 1038 | final Menu helpMenu = ActionUtils.createMenu( | |
| 1039 | get( "Main.menu.help" ), | |
| 1040 | helpAboutAction ); | |
| 1041 | ||
| 1042 | final MenuBar menuBar = new MenuBar( | |
| 1043 | fileMenu, | |
| 1044 | editMenu, | |
| 1045 | insertMenu, | |
| 1046 | helpMenu ); | |
| 1047 | ||
| 1048 | //---- ToolBar ---- | |
| 1049 | final ToolBar toolBar = ActionUtils.createToolBar( | |
| 1050 | fileNewAction, | |
| 1051 | fileOpenAction, | |
| 1052 | fileSaveAction, | |
| 1053 | null, | |
| 1054 | editUndoAction, | |
| 1055 | editRedoAction, | |
| 1056 | null, | |
| 1057 | insertBoldAction, | |
| 1058 | insertItalicAction, | |
| 1059 | insertSuperscriptAction, | |
| 1060 | insertSubscriptAction, | |
| 1061 | insertBlockquoteAction, | |
| 1062 | insertCodeAction, | |
| 1063 | insertFencedCodeBlockAction, | |
| 1064 | null, | |
| 1065 | insertLinkAction, | |
| 1066 | insertImageAction, | |
| 1067 | null, | |
| 1068 | headers[ 0 ], | |
| 1069 | null, | |
| 1070 | insertUnorderedListAction, | |
| 1071 | insertOrderedListAction ); | |
| 1072 | ||
| 1073 | return new VBox( menuBar, toolBar ); | |
| 1074 | } | |
| 1075 | ||
| 1076 | private UserPreferences createUserPreferences() { | |
| 1077 | return new UserPreferences(); | |
| 1078 | } | |
| 1079 | ||
| 1080 | /** | |
| 1081 | * Creates a boolean property that is bound to another boolean value of the | |
| 1082 | * active editor. | |
| 1083 | */ | |
| 1084 | private BooleanProperty createActiveBooleanProperty( | |
| 1085 | final Function<FileEditorTab, ObservableBooleanValue> func ) { | |
| 1086 | ||
| 1087 | final BooleanProperty b = new SimpleBooleanProperty(); | |
| 1088 | final FileEditorTab tab = getActiveFileEditor(); | |
| 1089 | ||
| 1090 | if( tab != null ) { | |
| 1091 | b.bind( func.apply( tab ) ); | |
| 1092 | } | |
| 1093 | ||
| 1094 | getFileEditorPane().activeFileEditorProperty().addListener( | |
| 1095 | ( observable, oldFileEditor, newFileEditor ) -> { | |
| 1096 | b.unbind(); | |
| 1097 | ||
| 1098 | if( newFileEditor == null ) { | |
| 1099 | b.set( false ); | |
| 1100 | } | |
| 1101 | else { | |
| 1102 | b.bind( func.apply( newFileEditor ) ); | |
| 1103 | } | |
| 1104 | } | |
| 1105 | ); | |
| 1106 | ||
| 1107 | return b; | |
| 1108 | } | |
| 1109 | ||
| 1110 | //---- Convenience accessors ---------------------------------------------- | |
| 1111 | ||
| 1112 | private Preferences getPreferences() { | |
| 1113 | return OPTIONS.getState(); | |
| 1114 | } | |
| 1115 | ||
| 1116 | private float getFloat( final String key, final float defaultValue ) { | |
| 1117 | return getPreferences().getFloat( key, defaultValue ); | |
| 1118 | } | |
| 1119 | ||
| 1120 | public Window getWindow() { | |
| 1121 | return getScene().getWindow(); | |
| 1122 | } | |
| 1123 | ||
| 1124 | private MarkdownEditorPane getActiveEditor() { | |
| 1125 | final EditorPane pane = getActiveFileEditor().getEditorPane(); | |
| 1126 | ||
| 1127 | return pane instanceof MarkdownEditorPane | |
| 1128 | ? (MarkdownEditorPane) pane | |
| 1129 | : new MarkdownEditorPane(); | |
| 1130 | } | |
| 1131 | ||
| 1132 | private FileEditorTab getActiveFileEditor() { | |
| 1133 | return getFileEditorPane().getActiveFileEditor(); | |
| 1134 | } | |
| 1135 | ||
| 1136 | //---- Member accessors --------------------------------------------------- | |
| 1137 | ||
| 1138 | protected Scene getScene() { | |
| 1139 | return mScene; | |
| 1140 | } | |
| 1141 | ||
| 1142 | private Map<FileEditorTab, Processor<String>> getProcessors() { | |
| 1143 | return mProcessors; | |
| 1144 | } | |
| 1145 | ||
| 1146 | private FileEditorTabPane getFileEditorPane() { | |
| 1147 | if( this.fileEditorPane == null ) { | |
| 1148 | this.fileEditorPane = createFileEditorPane(); | |
| 1149 | } | |
| 1150 | ||
| 1151 | return this.fileEditorPane; | |
| 1152 | } | |
| 1153 | ||
| 1154 | private HTMLPreviewPane getPreviewPane() { | |
| 1155 | return mPreviewPane; | |
| 1156 | } | |
| 1157 | ||
| 1158 | private void setDefinitionSource( final DefinitionSource definitionSource ) { | |
| 1159 | assert definitionSource != null; | |
| 1160 | mDefinitionSource = definitionSource; | |
| 1161 | } | |
| 1162 | ||
| 1163 | private DefinitionSource getDefinitionSource() { | |
| 1164 | return mDefinitionSource; | |
| 1165 | } | |
| 1166 | ||
| 1167 | private DefinitionPane getDefinitionPane() { | |
| 1168 | return mDefinitionPane; | |
| 1169 | } | |
| 1170 | ||
| 1171 | private Notifier getNotifier() { | |
| 1172 | return NOTIFIER; | |
| 1173 | } | |
| 1174 | ||
| 1175 | private Text getLineNumberText() { | |
| 1176 | return mLineNumberText; | |
| 1177 | } | |
| 1178 | ||
| 1179 | private StatusBar getStatusBar() { | |
| 1180 | return mStatusBar; | |
| 1181 | } | |
| 1182 | ||
| 1183 | private TextField getFindTextField() { | |
| 1184 | return mFindTextField; | |
| 1185 | } | |
| 1186 | ||
| 1187 | /** | |
| 1188 | * Returns the variable map of interpolated definitions. | |
| 1189 | * | |
| 1190 | * @return A map to help dereference variables. | |
| 1191 | */ | |
| 1192 | private Map<String, String> getResolvedMap() { | |
| 1193 | return mResolvedMap; | |
| 1194 | } | |
| 1195 | ||
| 1196 | //---- Persistence accessors ---------------------------------------------- | |
| 1197 | private UserPreferences getUserPreferences() { | |
| 1198 | return OPTIONS.getUserPreferences(); | |
| 1199 | } | |
| 1200 | ||
| 1201 | private Path getDefinitionPath() { | |
| 1202 | return getUserPreferences().getDefinitionPath(); | |
| 1203 | } | |
| 1204 | ||
| 1205 | private File getImagesDirectory() { | |
| 1206 | return getUserPreferences().getImagesDirectory(); | |
| 1207 | } | |
| 1208 | ||
| 1209 | private String getImagesOrder() { | |
| 1210 | return getUserPreferences().getImagesOrder(); | |
| 1211 | } | |
| 1212 | } | |
| 1 | 1213 |
| 1 | /* | |
| 2 | * Copyright (c) 2016 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * * Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * * Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar; | |
| 28 | ||
| 29 | import java.text.MessageFormat; | |
| 30 | import java.util.ResourceBundle; | |
| 31 | import java.util.Stack; | |
| 32 | ||
| 33 | import static com.scrivenvar.Constants.APP_BUNDLE_NAME; | |
| 34 | import static java.util.ResourceBundle.getBundle; | |
| 35 | ||
| 36 | /** | |
| 37 | * Recursively resolves message properties. Property values can refer to other | |
| 38 | * properties using a <code>${var}</code> syntax. | |
| 39 | * | |
| 40 | * @author Karl Tauber, Dave Jarvis | |
| 41 | */ | |
| 42 | public class Messages { | |
| 43 | ||
| 44 | private static final ResourceBundle RESOURCE_BUNDLE = | |
| 45 | getBundle( APP_BUNDLE_NAME ); | |
| 46 | ||
| 47 | private Messages() { | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Return the value of a resource bundle value after having resolved any | |
| 52 | * references to other bundle variables. | |
| 53 | * | |
| 54 | * @param props The bundle containing resolvable properties. | |
| 55 | * @param s The value for a key to resolve. | |
| 56 | * @return The value of the key with all references recursively dereferenced. | |
| 57 | */ | |
| 58 | @SuppressWarnings("SameParameterValue") | |
| 59 | private static String resolve( final ResourceBundle props, final String s ) { | |
| 60 | final int len = s.length(); | |
| 61 | final Stack<StringBuilder> stack = new Stack<>(); | |
| 62 | ||
| 63 | StringBuilder sb = new StringBuilder( 256 ); | |
| 64 | boolean open = false; | |
| 65 | ||
| 66 | for( int i = 0; i < len; i++ ) { | |
| 67 | final char c = s.charAt( i ); | |
| 68 | ||
| 69 | switch( c ) { | |
| 70 | case '$': { | |
| 71 | if( i + 1 < len && s.charAt( i + 1 ) == '{' ) { | |
| 72 | stack.push( sb ); | |
| 73 | sb = new StringBuilder( 256 ); | |
| 74 | i++; | |
| 75 | open = true; | |
| 76 | } | |
| 77 | ||
| 78 | break; | |
| 79 | } | |
| 80 | ||
| 81 | case '}': { | |
| 82 | if( open ) { | |
| 83 | open = false; | |
| 84 | final String name = sb.toString(); | |
| 85 | ||
| 86 | sb = stack.pop(); | |
| 87 | sb.append( props.getString( name ) ); | |
| 88 | break; | |
| 89 | } | |
| 90 | } | |
| 91 | ||
| 92 | default: { | |
| 93 | sb.append( c ); | |
| 94 | break; | |
| 95 | } | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | if( open ) { | |
| 100 | throw new IllegalArgumentException( "missing '}'" ); | |
| 101 | } | |
| 102 | ||
| 103 | return sb.toString(); | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Returns the value for a key from the message bundle. | |
| 108 | * | |
| 109 | * @param key Retrieve the value for this key. | |
| 110 | * @return The value for the key. | |
| 111 | */ | |
| 112 | public static String get( final String key ) { | |
| 113 | try { | |
| 114 | return resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) ); | |
| 115 | } catch( final Exception ex ) { | |
| 116 | return key; | |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | public static String getLiteral( final String key ) { | |
| 121 | return RESOURCE_BUNDLE.getString( key ); | |
| 122 | } | |
| 123 | ||
| 124 | public static String get( final String key, final boolean interpolate ) { | |
| 125 | return interpolate ? get( key ) : getLiteral( key ); | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Returns the value for a key from the message bundle with the arguments | |
| 130 | * replacing <code>{#}</code> place holders. | |
| 131 | * | |
| 132 | * @param key Retrieve the value for this key. | |
| 133 | * @param args The values to substitute for place holders. | |
| 134 | * @return The value for the key. | |
| 135 | */ | |
| 136 | public static String get( final String key, final Object... args ) { | |
| 137 | return MessageFormat.format( get( key ), args ); | |
| 138 | } | |
| 139 | } | |
| 1 | 140 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import java.util.HashMap; | |
| 31 | import java.util.Map; | |
| 32 | import java.util.ServiceLoader; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for loading services. The services are treated as singleton | |
| 36 | * instances. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class Services { | |
| 41 | ||
| 42 | @SuppressWarnings("rawtypes") | |
| 43 | private static final Map<Class, Object> SINGLETONS = new HashMap<>(); | |
| 44 | ||
| 45 | /** | |
| 46 | * Loads a service based on its interface definition. This will return an | |
| 47 | * existing instance if the class has already been instantiated. | |
| 48 | * | |
| 49 | * @param <T> The service to load. | |
| 50 | * @param api The interface definition for the service. | |
| 51 | * @return A class that implements the interface. | |
| 52 | */ | |
| 53 | @SuppressWarnings("unchecked") | |
| 54 | public static <T> T load( final Class<T> api ) { | |
| 55 | final T o = (T) get( api ); | |
| 56 | ||
| 57 | return o == null ? newInstance( api ) : o; | |
| 58 | } | |
| 59 | ||
| 60 | private static <T> T newInstance( final Class<T> api ) { | |
| 61 | final ServiceLoader<T> services = ServiceLoader.load( api ); | |
| 62 | ||
| 63 | for( final T service : services ) { | |
| 64 | if( service != null ) { | |
| 65 | // Re-use the same instance the next time the class is loaded. | |
| 66 | put( api, service ); | |
| 67 | return service; | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | throw new RuntimeException( "No implementation for: " + api ); | |
| 72 | } | |
| 73 | ||
| 74 | @SuppressWarnings("rawtypes") | |
| 75 | private static void put( final Class key, Object value ) { | |
| 76 | SINGLETONS.put( key, value ); | |
| 77 | } | |
| 78 | ||
| 79 | @SuppressWarnings("rawtypes") | |
| 80 | private static Object get( final Class api ) { | |
| 81 | return SINGLETONS.get( api ); | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import com.scrivenvar.Messages; | |
| 31 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 32 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 33 | import javafx.event.ActionEvent; | |
| 34 | import javafx.scene.control.Tooltip; | |
| 35 | import javafx.stage.DirectoryChooser; | |
| 36 | ||
| 37 | import java.io.File; | |
| 38 | ||
| 39 | /** | |
| 40 | * Button that opens a directory chooser to select a local directory for a | |
| 41 | * URL in markdown. | |
| 42 | * | |
| 43 | * @author Karl Tauber | |
| 44 | */ | |
| 45 | public class BrowseDirectoryButton | |
| 46 | extends BrowseFileButton { | |
| 47 | public BrowseDirectoryButton() { | |
| 48 | setGraphic( FontAwesomeIconFactory.get() | |
| 49 | .createIcon( FontAwesomeIcon.FOLDER_ALT, | |
| 50 | "1.2em" ) ); | |
| 51 | setTooltip( new Tooltip( Messages.get( "BrowseDirectoryButton.tooltip" ) ) ); | |
| 52 | } | |
| 53 | ||
| 54 | @Override | |
| 55 | protected void browse( ActionEvent e ) { | |
| 56 | DirectoryChooser directoryChooser = new DirectoryChooser(); | |
| 57 | directoryChooser.setTitle( Messages.get( | |
| 58 | "BrowseDirectoryButton.chooser.title" ) ); | |
| 59 | directoryChooser.setInitialDirectory( getInitialDirectory() ); | |
| 60 | File result = directoryChooser.showDialog( getScene().getWindow() ); | |
| 61 | if( result != null ) { | |
| 62 | updateUrl( result ); | |
| 63 | } | |
| 64 | } | |
| 65 | } | |
| 1 | 66 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.Path; | |
| 32 | import java.util.ArrayList; | |
| 33 | import java.util.List; | |
| 34 | import javafx.beans.property.ObjectProperty; | |
| 35 | import javafx.beans.property.SimpleObjectProperty; | |
| 36 | import javafx.event.ActionEvent; | |
| 37 | import javafx.scene.control.Button; | |
| 38 | import javafx.scene.control.Tooltip; | |
| 39 | import javafx.scene.input.KeyCode; | |
| 40 | import javafx.scene.input.KeyEvent; | |
| 41 | import javafx.stage.FileChooser; | |
| 42 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 43 | import com.scrivenvar.Messages; | |
| 44 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 45 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 46 | ||
| 47 | /** | |
| 48 | * Button that opens a file chooser to select a local file for a URL in markdown. | |
| 49 | * | |
| 50 | * @author Karl Tauber | |
| 51 | */ | |
| 52 | public class BrowseFileButton | |
| 53 | extends Button | |
| 54 | { | |
| 55 | private final List<ExtensionFilter> extensionFilters = new ArrayList<>(); | |
| 56 | ||
| 57 | public BrowseFileButton() { | |
| 58 | setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FILE_ALT, "1.2em")); | |
| 59 | setTooltip(new Tooltip(Messages.get("BrowseFileButton.tooltip"))); | |
| 60 | setOnAction(this::browse); | |
| 61 | ||
| 62 | disableProperty().bind(basePath.isNull()); | |
| 63 | ||
| 64 | // workaround for a JavaFX bug: | |
| 65 | // avoid closing the dialog that contains this control when the user | |
| 66 | // closes the FileChooser or DirectoryChooser using the ESC key | |
| 67 | addEventHandler(KeyEvent.KEY_RELEASED, e-> { | |
| 68 | if (e.getCode() == KeyCode.ESCAPE) | |
| 69 | e.consume(); | |
| 70 | }); | |
| 71 | } | |
| 72 | ||
| 73 | public void addExtensionFilter(ExtensionFilter extensionFilter) { | |
| 74 | extensionFilters.add(extensionFilter); | |
| 75 | } | |
| 76 | ||
| 77 | // 'basePath' property | |
| 78 | private final ObjectProperty<Path> basePath = new SimpleObjectProperty<>(); | |
| 79 | public Path getBasePath() { return basePath.get(); } | |
| 80 | public void setBasePath(Path basePath) { this.basePath.set(basePath); } | |
| 81 | public ObjectProperty<Path> basePathProperty() { return basePath; } | |
| 82 | ||
| 83 | // 'url' property | |
| 84 | private final ObjectProperty<String> url = new SimpleObjectProperty<>(); | |
| 85 | public String getUrl() { return url.get(); } | |
| 86 | public void setUrl(String url) { this.url.set(url); } | |
| 87 | public ObjectProperty<String> urlProperty() { return url; } | |
| 88 | ||
| 89 | protected void browse(ActionEvent e) { | |
| 90 | FileChooser fileChooser = new FileChooser(); | |
| 91 | fileChooser.setTitle(Messages.get("BrowseFileButton.chooser.title")); | |
| 92 | fileChooser.getExtensionFilters().addAll(extensionFilters); | |
| 93 | fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.get("BrowseFileButton.chooser.allFilesFilter"), "*.*")); | |
| 94 | fileChooser.setInitialDirectory(getInitialDirectory()); | |
| 95 | File result = fileChooser.showOpenDialog(getScene().getWindow()); | |
| 96 | if (result != null) | |
| 97 | updateUrl(result); | |
| 98 | } | |
| 99 | ||
| 100 | protected File getInitialDirectory() { | |
| 101 | //TODO build initial directory based on current value of 'url' property | |
| 102 | return getBasePath().toFile(); | |
| 103 | } | |
| 104 | ||
| 105 | protected void updateUrl(File file) { | |
| 106 | String newUrl; | |
| 107 | try { | |
| 108 | newUrl = getBasePath().relativize(file.toPath()).toString(); | |
| 109 | } catch (IllegalArgumentException ex) { | |
| 110 | newUrl = file.toString(); | |
| 111 | } | |
| 112 | url.set(newUrl.replace('\\', '/')); | |
| 113 | } | |
| 114 | } | |
| 1 | 115 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.scene.control.TextField; | |
| 33 | import javafx.util.StringConverter; | |
| 34 | ||
| 35 | /** | |
| 36 | * TextField that can escape/unescape characters for markdown. | |
| 37 | * | |
| 38 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class EscapeTextField extends TextField { | |
| 41 | ||
| 42 | public EscapeTextField() { | |
| 43 | escapedText.bindBidirectional( | |
| 44 | textProperty(), | |
| 45 | new StringConverter<>() { | |
| 46 | @Override | |
| 47 | public String toString( String object ) { | |
| 48 | return escape( object ); | |
| 49 | } | |
| 50 | ||
| 51 | @Override | |
| 52 | public String fromString( String string ) { | |
| 53 | return unescape( string ); | |
| 54 | } | |
| 55 | } | |
| 56 | ); | |
| 57 | escapeCharacters.addListener( | |
| 58 | e -> escapedText.set( escape( textProperty().get() ) ) | |
| 59 | ); | |
| 60 | } | |
| 61 | ||
| 62 | // 'escapedText' property | |
| 63 | private final StringProperty escapedText = new SimpleStringProperty(); | |
| 64 | ||
| 65 | public StringProperty escapedTextProperty() { | |
| 66 | return escapedText; | |
| 67 | } | |
| 68 | ||
| 69 | // 'escapeCharacters' property | |
| 70 | private final StringProperty escapeCharacters = new SimpleStringProperty(); | |
| 71 | ||
| 72 | public String getEscapeCharacters() { | |
| 73 | return escapeCharacters.get(); | |
| 74 | } | |
| 75 | ||
| 76 | public void setEscapeCharacters( String escapeCharacters ) { | |
| 77 | this.escapeCharacters.set( escapeCharacters ); | |
| 78 | } | |
| 79 | ||
| 80 | private String escape( final String s ) { | |
| 81 | final String escapeChars = getEscapeCharacters(); | |
| 82 | ||
| 83 | return isEmpty( escapeChars ) ? s : | |
| 84 | s.replaceAll( "([" + escapeChars.replaceAll( | |
| 85 | "(.)", | |
| 86 | "\\\\$1" ) + "])", "\\\\$1" ); | |
| 87 | } | |
| 88 | ||
| 89 | private String unescape( final String s ) { | |
| 90 | final String escapeChars = getEscapeCharacters(); | |
| 91 | ||
| 92 | return isEmpty( escapeChars ) ? s : | |
| 93 | s.replaceAll( "\\\\([" + escapeChars | |
| 94 | .replaceAll( "(.)", "\\\\$1" ) + "])", "$1" ); | |
| 95 | } | |
| 96 | ||
| 97 | private static boolean isEmpty( final String s ) { | |
| 98 | return s == null || s.isEmpty(); | |
| 99 | } | |
| 100 | } | |
| 1 | 101 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.controls; | |
| 28 | ||
| 29 | import javafx.beans.property.IntegerProperty; | |
| 30 | import javafx.beans.property.SimpleIntegerProperty; | |
| 31 | import javafx.scene.control.CheckBox; | |
| 32 | ||
| 33 | /** | |
| 34 | * CheckBox that toggles a bit in an integer. | |
| 35 | * | |
| 36 | * @author Karl Tauber | |
| 37 | */ | |
| 38 | public class FlagCheckBox extends CheckBox { | |
| 39 | ||
| 40 | public FlagCheckBox() { | |
| 41 | setOnAction( e -> { | |
| 42 | if( isSelected() ) { | |
| 43 | setFlags( getFlags() | getFlag() ); | |
| 44 | } else { | |
| 45 | setFlags( getFlags() & ~getFlag() ); | |
| 46 | } | |
| 47 | } ); | |
| 48 | ||
| 49 | flags.addListener( (obs, oldFlags, newFlags) -> { | |
| 50 | setSelected( (newFlags.intValue() & getFlag()) != 0 ); | |
| 51 | } ); | |
| 52 | } | |
| 53 | ||
| 54 | // 'flag' property | |
| 55 | private final IntegerProperty flag = new SimpleIntegerProperty(); | |
| 56 | ||
| 57 | public int getFlag() { | |
| 58 | return flag.get(); | |
| 59 | } | |
| 60 | ||
| 61 | public void setFlag( int flag ) { | |
| 62 | this.flag.set( flag ); | |
| 63 | } | |
| 64 | ||
| 65 | public IntegerProperty flagProperty() { | |
| 66 | return flag; | |
| 67 | } | |
| 68 | ||
| 69 | // 'flags' property | |
| 70 | private final IntegerProperty flags = new SimpleIntegerProperty(); | |
| 71 | ||
| 72 | public int getFlags() { | |
| 73 | return flags.get(); | |
| 74 | } | |
| 75 | ||
| 76 | public void setFlags( int flags ) { | |
| 77 | this.flags.set( flags ); | |
| 78 | } | |
| 79 | ||
| 80 | public IntegerProperty flagsProperty() { | |
| 81 | return flags; | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.controls; | |
| 28 | ||
| 29 | import com.scrivenvar.Main; | |
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.scene.control.Hyperlink; | |
| 33 | ||
| 34 | /** | |
| 35 | * Opens a web site in the default web browser. | |
| 36 | * | |
| 37 | * @author Karl Tauber | |
| 38 | */ | |
| 39 | public class WebHyperlink extends Hyperlink { | |
| 40 | ||
| 41 | // 'uri' property | |
| 42 | private final StringProperty uri = new SimpleStringProperty(); | |
| 43 | ||
| 44 | public WebHyperlink() { | |
| 45 | setStyle( "-fx-padding: 0; -fx-border-width: 0" ); | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public void fire() { | |
| 50 | Main.showDocument( getUri() ); | |
| 51 | } | |
| 52 | ||
| 53 | public String getUri() { | |
| 54 | return uri.get(); | |
| 55 | } | |
| 56 | ||
| 57 | public void setUri( String uri ) { | |
| 58 | this.uri.set( uri ); | |
| 59 | } | |
| 60 | ||
| 61 | public StringProperty uriProperty() { | |
| 62 | return uri; | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Brackets variable names with <code>`r#</code> and <code>`</code>. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class RVariableDecorator implements VariableDecorator { | |
| 36 | public static final String PREFIX = "`r#"; | |
| 37 | public static final char SUFFIX = '`'; | |
| 38 | ||
| 39 | /** | |
| 40 | * Returns the given string R-escaping backticks prepended and appended. This | |
| 41 | * is not null safe. Do not pass null into this method. | |
| 42 | * | |
| 43 | * @param variableName The string to decorate. | |
| 44 | * @return "`r#" + variableName + "`". | |
| 45 | */ | |
| 46 | @Override | |
| 47 | public String decorate( String variableName ) { | |
| 48 | assert variableName != null; | |
| 49 | ||
| 50 | // Delete the $ $ sigils from Markdown variables. | |
| 51 | if( variableName.length() > 1 ) { | |
| 52 | variableName = variableName.substring( 1, variableName.length() - 1 ); | |
| 53 | } | |
| 54 | ||
| 55 | return PREFIX + | |
| 56 | "x( v$" + | |
| 57 | variableName.replace( '.', '$' ) + | |
| 58 | " )" + | |
| 59 | SUFFIX; | |
| 60 | } | |
| 61 | } | |
| 1 | 62 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for updating variable names to use a machine-readable format | |
| 32 | * corresponding to the type of file being edited. | |
| 33 | */ | |
| 34 | public interface VariableDecorator { | |
| 35 | ||
| 36 | /** | |
| 37 | * This decorates a variable name based on some criteria determined by the | |
| 38 | * factory that creates implementations of this interface. | |
| 39 | * | |
| 40 | * @param variableName The text to decorate as per the filename extension | |
| 41 | * would indicate (e.g., ".md" goes to $VAR$ while " | |
| 42 | * .Rmd" goes to `r#VAR`). | |
| 43 | * @return The given variable name modified with its requisite delimiters. | |
| 44 | */ | |
| 45 | String decorate( String variableName ); | |
| 46 | } | |
| 1 | 47 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Brackets variable names with dollar symbols. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class YamlVariableDecorator implements VariableDecorator { | |
| 36 | ||
| 37 | /** | |
| 38 | * Returns the given {@link String} verbatim because variables in YAML | |
| 39 | * documents and plain Markdown documents already have the appropriate | |
| 40 | * tokenizable syntax wrapped around the text. | |
| 41 | * | |
| 42 | * @param variableName Returned verbatim. | |
| 43 | */ | |
| 44 | @Override | |
| 45 | public String decorate( final String variableName ) { | |
| 46 | assert variableName != null; | |
| 47 | return variableName; | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Sigilifies the given key. | |
| 52 | * | |
| 53 | * @param key The key to adorn with YAML variable sigil characters. | |
| 54 | * @return The given key bracketed by dollar symbols. | |
| 55 | */ | |
| 56 | public static String entoken( final String key ) { | |
| 57 | assert key != null; | |
| 58 | return '$' + key + '$'; | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.FileType; | |
| 32 | import com.scrivenvar.definition.yaml.YamlDefinitionSource; | |
| 33 | import com.scrivenvar.util.ProtocolResolver; | |
| 34 | ||
| 35 | import java.nio.file.Path; | |
| 36 | ||
| 37 | import static com.scrivenvar.Constants.DEFINITION_PROTOCOL_FILE; | |
| 38 | import static com.scrivenvar.Constants.GLOB_PREFIX_DEFINITION; | |
| 39 | import static com.scrivenvar.FileType.YAML; | |
| 40 | ||
| 41 | /** | |
| 42 | * Responsible for creating objects that can read and write definition data | |
| 43 | * sources. The data source could be YAML, TOML, JSON, flat files, or from a | |
| 44 | * database. | |
| 45 | * | |
| 46 | * @author White Magic Software, Ltd. | |
| 47 | */ | |
| 48 | public class DefinitionFactory extends AbstractFileFactory { | |
| 49 | ||
| 50 | /** | |
| 51 | * Default (empty) constructor. | |
| 52 | */ | |
| 53 | public DefinitionFactory() { | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Creates a definition source capable of reading definitions from the given | |
| 58 | * path. | |
| 59 | * | |
| 60 | * @param path Path to a resource containing definitions. | |
| 61 | * @return The definition source appropriate for the given path. | |
| 62 | */ | |
| 63 | public DefinitionSource createDefinitionSource( final Path path ) { | |
| 64 | assert path != null; | |
| 65 | ||
| 66 | final String protocol = ProtocolResolver.getProtocol( path.toString() ); | |
| 67 | DefinitionSource result = null; | |
| 68 | ||
| 69 | if( DEFINITION_PROTOCOL_FILE.equals( protocol ) ) { | |
| 70 | final FileType filetype = lookup( path, GLOB_PREFIX_DEFINITION ); | |
| 71 | result = createFileDefinitionSource( filetype, path ); | |
| 72 | } | |
| 73 | else { | |
| 74 | unknownFileType( protocol, path.toString() ); | |
| 75 | } | |
| 76 | ||
| 77 | return result; | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Creates a definition source based on the file type. | |
| 82 | * | |
| 83 | * @param filetype Property key name suffix from settings.properties file. | |
| 84 | * @param path Path to the file that corresponds to the extension. | |
| 85 | * @return A DefinitionSource capable of parsing the data stored at the path. | |
| 86 | */ | |
| 87 | private DefinitionSource createFileDefinitionSource( | |
| 88 | final FileType filetype, final Path path ) { | |
| 89 | assert filetype != null; | |
| 90 | assert path != null; | |
| 91 | ||
| 92 | if( filetype == YAML ) { | |
| 93 | return new YamlDefinitionSource( path ); | |
| 94 | } | |
| 95 | ||
| 96 | throw new IllegalArgumentException( filetype.toString() ); | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.collections.ObservableList; | |
| 33 | import javafx.event.Event; | |
| 34 | import javafx.event.EventHandler; | |
| 35 | import javafx.scene.Node; | |
| 36 | import javafx.scene.control.*; | |
| 37 | import javafx.scene.control.cell.TextFieldTreeCell; | |
| 38 | import javafx.scene.input.KeyEvent; | |
| 39 | import javafx.util.StringConverter; | |
| 40 | ||
| 41 | import java.util.*; | |
| 42 | ||
| 43 | import static com.scrivenvar.Messages.get; | |
| 44 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 45 | ||
| 46 | /** | |
| 47 | * Provides the user interface that holds a {@link TreeView}, which | |
| 48 | * allows users to interact with key/value pairs loaded from the | |
| 49 | * {@link DocumentParser} and adapted using a {@link TreeAdapter}. | |
| 50 | * | |
| 51 | * @author White Magic Software, Ltd. | |
| 52 | */ | |
| 53 | public final class DefinitionPane extends TitledPane { | |
| 54 | ||
| 55 | /** | |
| 56 | * Trimmed off the end of a word to match a variable name. | |
| 57 | */ | |
| 58 | private final static String TERMINALS = ":;,.!?-/\\¡¿"; | |
| 59 | ||
| 60 | /** | |
| 61 | * Contains a view of the definitions. | |
| 62 | */ | |
| 63 | private final TreeView<String> mTreeView = new TreeView<>(); | |
| 64 | ||
| 65 | /** | |
| 66 | * Handlers for key press events. | |
| 67 | */ | |
| 68 | private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers | |
| 69 | = new HashSet<>(); | |
| 70 | ||
| 71 | /** | |
| 72 | * Definition file name shown in the title of the pane. | |
| 73 | */ | |
| 74 | private final StringProperty mFilename = new SimpleStringProperty(); | |
| 75 | ||
| 76 | /** | |
| 77 | * Constructs a definition pane with a given tree view root. | |
| 78 | */ | |
| 79 | public DefinitionPane() { | |
| 80 | final var treeView = getTreeView(); | |
| 81 | treeView.setEditable( true ); | |
| 82 | treeView.setCellFactory( cell -> createTreeCell() ); | |
| 83 | treeView.setContextMenu( createContextMenu() ); | |
| 84 | treeView.addEventFilter( KEY_PRESSED, this::keyEventFilter ); | |
| 85 | treeView.setShowRoot( false ); | |
| 86 | getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE ); | |
| 87 | ||
| 88 | textProperty().bind( mFilename ); | |
| 89 | ||
| 90 | setContent( treeView ); | |
| 91 | setCollapsible( false ); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Changes the root of the {@link TreeView} to the root of the | |
| 96 | * {@link TreeView} from the {@link DefinitionSource}. | |
| 97 | * | |
| 98 | * @param definitionSource Container for the hierarchy of key/value pairs | |
| 99 | * to replace the existing hierarchy. | |
| 100 | */ | |
| 101 | public void update( final DefinitionSource definitionSource ) { | |
| 102 | assert definitionSource != null; | |
| 103 | ||
| 104 | final TreeAdapter treeAdapter = definitionSource.getTreeAdapter(); | |
| 105 | final TreeItem<String> root = treeAdapter.adapt( | |
| 106 | get( "Pane.definition.node.root.title" ) | |
| 107 | ); | |
| 108 | ||
| 109 | getTreeView().setRoot( root ); | |
| 110 | } | |
| 111 | ||
| 112 | public Map<String, String> toMap() { | |
| 113 | return TreeItemAdapter.toMap( getTreeView().getRoot() ); | |
| 114 | } | |
| 115 | ||
| 116 | /** | |
| 117 | * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView} | |
| 118 | * is modified. The modifications include: item value changes, item additions, | |
| 119 | * and item removals. | |
| 120 | * <p> | |
| 121 | * Safe to call multiple times; if a handler is already registered, the | |
| 122 | * old handler is used. | |
| 123 | * </p> | |
| 124 | * | |
| 125 | * @param handler The handler to call whenever any {@link TreeItem} changes. | |
| 126 | */ | |
| 127 | public void addTreeChangeHandler( | |
| 128 | final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) { | |
| 129 | final TreeItem<String> root = getTreeView().getRoot(); | |
| 130 | root.addEventHandler( TreeItem.valueChangedEvent(), handler ); | |
| 131 | root.addEventHandler( TreeItem.childrenModificationEvent(), handler ); | |
| 132 | } | |
| 133 | ||
| 134 | public void addKeyEventHandler( | |
| 135 | final EventHandler<? super KeyEvent> handler ) { | |
| 136 | getKeyEventHandlers().add( handler ); | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably | |
| 141 | * well-formed for export. A tree is considered well-formed if the following | |
| 142 | * conditions are met: | |
| 143 | * | |
| 144 | * <ul> | |
| 145 | * <li>The root node contains at least one child node having a leaf.</li> | |
| 146 | * <li>There are no leaf nodes with sibling leaf nodes.</li> | |
| 147 | * </ul> | |
| 148 | * | |
| 149 | * @return {@code null} if the document is well-formed, otherwise the | |
| 150 | * problematic child {@link TreeItem}. | |
| 151 | */ | |
| 152 | public TreeItem<String> isTreeWellFormed() { | |
| 153 | final var root = getTreeView().getRoot(); | |
| 154 | ||
| 155 | for( final var child : root.getChildren() ) { | |
| 156 | final var problemChild = isWellFormed( child ); | |
| 157 | ||
| 158 | if( child.isLeaf() || problemChild != null ) { | |
| 159 | return problemChild; | |
| 160 | } | |
| 161 | } | |
| 162 | ||
| 163 | return null; | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Determines whether the document is well-formed by ensuring that | |
| 168 | * child branches do not contain multiple leaves. | |
| 169 | * | |
| 170 | * @param item The sub-tree to check for well-formedness. | |
| 171 | * @return {@code null} when the tree is well-formed, otherwise the | |
| 172 | * problematic {@link TreeItem}. | |
| 173 | */ | |
| 174 | private TreeItem<String> isWellFormed( final TreeItem<String> item ) { | |
| 175 | int childLeafs = 0; | |
| 176 | int childBranches = 0; | |
| 177 | ||
| 178 | for( final TreeItem<String> child : item.getChildren() ) { | |
| 179 | if( child.isLeaf() ) { | |
| 180 | childLeafs++; | |
| 181 | } | |
| 182 | else { | |
| 183 | childBranches++; | |
| 184 | } | |
| 185 | ||
| 186 | final var problemChild = isWellFormed( child ); | |
| 187 | ||
| 188 | if( problemChild != null ) { | |
| 189 | return problemChild; | |
| 190 | } | |
| 191 | } | |
| 192 | ||
| 193 | return ((childBranches > 0 && childLeafs == 0) || | |
| 194 | (childBranches == 0 && childLeafs <= 1)) ? null : item; | |
| 195 | } | |
| 196 | ||
| 197 | /** | |
| 198 | * Returns the leaf that matches the given value. If the value is terminally | |
| 199 | * punctuated, the punctuation is removed if no match was found. | |
| 200 | * | |
| 201 | * @param value The value to find, never null. | |
| 202 | * @param findMode Defines how to match words. | |
| 203 | * @return The leaf that contains the given value, or null if neither the | |
| 204 | * original value nor the terminally-trimmed value was found. | |
| 205 | */ | |
| 206 | public VariableTreeItem<String> findLeaf( | |
| 207 | final String value, final FindMode findMode ) { | |
| 208 | final VariableTreeItem<String> root = getTreeRoot(); | |
| 209 | final VariableTreeItem<String> leaf = root.findLeaf( value, findMode ); | |
| 210 | ||
| 211 | return leaf == null | |
| 212 | ? root.findLeaf( rtrimTerminalPunctuation( value ) ) | |
| 213 | : leaf; | |
| 214 | } | |
| 215 | ||
| 216 | /** | |
| 217 | * Removes punctuation from the end of a string. | |
| 218 | * | |
| 219 | * @param s The string to trim, never null. | |
| 220 | * @return The string trimmed of all terminal characters from the end | |
| 221 | */ | |
| 222 | private String rtrimTerminalPunctuation( final String s ) { | |
| 223 | assert s != null; | |
| 224 | int index = s.length() - 1; | |
| 225 | ||
| 226 | while( index > 0 && (TERMINALS.indexOf( s.charAt( index ) ) >= 0) ) { | |
| 227 | index--; | |
| 228 | } | |
| 229 | ||
| 230 | return s.substring( 0, index ); | |
| 231 | } | |
| 232 | ||
| 233 | /** | |
| 234 | * Expands the node to the root, recursively. | |
| 235 | * | |
| 236 | * @param <T> The type of tree item to expand (usually String). | |
| 237 | * @param node The node to expand. | |
| 238 | */ | |
| 239 | public <T> void expand( final TreeItem<T> node ) { | |
| 240 | if( node != null ) { | |
| 241 | expand( node.getParent() ); | |
| 242 | ||
| 243 | if( !node.isLeaf() ) { | |
| 244 | node.setExpanded( true ); | |
| 245 | } | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | public void select( final TreeItem<String> item ) { | |
| 250 | getSelectionModel().clearSelection(); | |
| 251 | getSelectionModel().select( getTreeView().getRow( item ) ); | |
| 252 | } | |
| 253 | ||
| 254 | /** | |
| 255 | * Collapses the tree, recursively. | |
| 256 | */ | |
| 257 | public void collapse() { | |
| 258 | collapse( getTreeRoot().getChildren() ); | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Collapses the tree, recursively. | |
| 263 | * | |
| 264 | * @param <T> The type of tree item to expand (usually String). | |
| 265 | * @param nodes The nodes to collapse. | |
| 266 | */ | |
| 267 | private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) { | |
| 268 | for( final TreeItem<T> node : nodes ) { | |
| 269 | node.setExpanded( false ); | |
| 270 | collapse( node.getChildren() ); | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | /** | |
| 275 | * @return {@code true} when the user is editing a {@link TreeItem}. | |
| 276 | */ | |
| 277 | private boolean isEditingTreeItem() { | |
| 278 | return getTreeView().editingItemProperty().getValue() != null; | |
| 279 | } | |
| 280 | ||
| 281 | /** | |
| 282 | * Changes to edit mode for the selected item. | |
| 283 | */ | |
| 284 | private void editSelectedItem() { | |
| 285 | getTreeView().edit( getSelectedItem() ); | |
| 286 | } | |
| 287 | ||
| 288 | /** | |
| 289 | * Removes all selected items from the {@link TreeView}. | |
| 290 | */ | |
| 291 | private void deleteSelectedItems() { | |
| 292 | for( final TreeItem<String> item : getSelectedItems() ) { | |
| 293 | final TreeItem<String> parent = item.getParent(); | |
| 294 | ||
| 295 | if( parent != null ) { | |
| 296 | parent.getChildren().remove( item ); | |
| 297 | } | |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | /** | |
| 302 | * Deletes the selected item. | |
| 303 | */ | |
| 304 | private void deleteSelectedItem() { | |
| 305 | final TreeItem<String> c = getSelectedItem(); | |
| 306 | getSiblings( c ).remove( c ); | |
| 307 | } | |
| 308 | ||
| 309 | /** | |
| 310 | * Adds a new item under the selected item (or root if nothing is selected). | |
| 311 | * There are a few conditions to consider: when adding to the root, | |
| 312 | * when adding to a leaf, and when adding to a non-leaf. Items added to the | |
| 313 | * root must contain two items: a key and a value. | |
| 314 | */ | |
| 315 | private void addItem() { | |
| 316 | final TreeItem<String> value = createTreeItem(); | |
| 317 | getSelectedItem().getChildren().add( value ); | |
| 318 | expand( value ); | |
| 319 | select( value ); | |
| 320 | } | |
| 321 | ||
| 322 | private ContextMenu createContextMenu() { | |
| 323 | final ContextMenu menu = new ContextMenu(); | |
| 324 | final ObservableList<MenuItem> items = menu.getItems(); | |
| 325 | ||
| 326 | addMenuItem( items, "Definition.menu.create" ) | |
| 327 | .setOnAction( e -> addItem() ); | |
| 328 | ||
| 329 | addMenuItem( items, "Definition.menu.rename" ) | |
| 330 | .setOnAction( e -> editSelectedItem() ); | |
| 331 | ||
| 332 | addMenuItem( items, "Definition.menu.remove" ) | |
| 333 | .setOnAction( e -> deleteSelectedItem() ); | |
| 334 | ||
| 335 | return menu; | |
| 336 | } | |
| 337 | ||
| 338 | /** | |
| 339 | * Executes hot-keys for edits to the definition tree. | |
| 340 | * | |
| 341 | * @param event Contains the key code of the key that was pressed. | |
| 342 | */ | |
| 343 | private void keyEventFilter( final KeyEvent event ) { | |
| 344 | if( !isEditingTreeItem() ) { | |
| 345 | switch( event.getCode() ) { | |
| 346 | case ENTER: | |
| 347 | expand( getSelectedItem() ); | |
| 348 | event.consume(); | |
| 349 | break; | |
| 350 | ||
| 351 | case DELETE: | |
| 352 | deleteSelectedItems(); | |
| 353 | break; | |
| 354 | ||
| 355 | case INSERT: | |
| 356 | addItem(); | |
| 357 | break; | |
| 358 | ||
| 359 | case R: | |
| 360 | if( event.isControlDown() ) { | |
| 361 | editSelectedItem(); | |
| 362 | } | |
| 363 | ||
| 364 | break; | |
| 365 | } | |
| 366 | ||
| 367 | for( final var handler : getKeyEventHandlers() ) { | |
| 368 | handler.handle( event ); | |
| 369 | } | |
| 370 | } | |
| 371 | } | |
| 372 | ||
| 373 | /** | |
| 374 | * Adds a menu item to a list of menu items. | |
| 375 | * | |
| 376 | * @param items The list of menu items to append to. | |
| 377 | * @param labelKey The resource bundle key name for the menu item's label. | |
| 378 | * @return The menu item added to the list of menu items. | |
| 379 | */ | |
| 380 | private MenuItem addMenuItem( | |
| 381 | final List<MenuItem> items, final String labelKey ) { | |
| 382 | final MenuItem menuItem = createMenuItem( labelKey ); | |
| 383 | items.add( menuItem ); | |
| 384 | return menuItem; | |
| 385 | } | |
| 386 | ||
| 387 | private MenuItem createMenuItem( final String labelKey ) { | |
| 388 | return new MenuItem( get( labelKey ) ); | |
| 389 | } | |
| 390 | ||
| 391 | private VariableTreeItem<String> createTreeItem() { | |
| 392 | return new VariableTreeItem<>( get( "Definition.menu.add.default" ) ); | |
| 393 | } | |
| 394 | ||
| 395 | private TreeCell<String> createTreeCell() { | |
| 396 | return new TextFieldTreeCell<>( | |
| 397 | createStringConverter() ) { | |
| 398 | @Override | |
| 399 | public void commitEdit( final String newValue ) { | |
| 400 | super.commitEdit( newValue ); | |
| 401 | select( getTreeItem() ); | |
| 402 | requestFocus(); | |
| 403 | } | |
| 404 | }; | |
| 405 | } | |
| 406 | ||
| 407 | @Override | |
| 408 | public void requestFocus() { | |
| 409 | super.requestFocus(); | |
| 410 | getTreeView().requestFocus(); | |
| 411 | } | |
| 412 | ||
| 413 | private StringConverter<String> createStringConverter() { | |
| 414 | return new StringConverter<>() { | |
| 415 | @Override | |
| 416 | public String toString( final String object ) { | |
| 417 | return object == null ? "" : object; | |
| 418 | } | |
| 419 | ||
| 420 | @Override | |
| 421 | public String fromString( final String string ) { | |
| 422 | return string == null ? "" : string; | |
| 423 | } | |
| 424 | }; | |
| 425 | } | |
| 426 | ||
| 427 | /** | |
| 428 | * Returns the tree view that contains the definition hierarchy. | |
| 429 | * | |
| 430 | * @return A non-null instance. | |
| 431 | */ | |
| 432 | public TreeView<String> getTreeView() { | |
| 433 | return mTreeView; | |
| 434 | } | |
| 435 | ||
| 436 | /** | |
| 437 | * Returns this pane. | |
| 438 | * | |
| 439 | * @return this | |
| 440 | */ | |
| 441 | public Node getNode() { | |
| 442 | return this; | |
| 443 | } | |
| 444 | ||
| 445 | /** | |
| 446 | * Returns the property used to set the title of the pane: the file name. | |
| 447 | * | |
| 448 | * @return A non-null property used for showing the definition file name. | |
| 449 | */ | |
| 450 | public StringProperty filenameProperty() { | |
| 451 | return mFilename; | |
| 452 | } | |
| 453 | ||
| 454 | /** | |
| 455 | * Returns the root of the tree. | |
| 456 | * | |
| 457 | * @return The first node added to the definition tree. | |
| 458 | */ | |
| 459 | private VariableTreeItem<String> getTreeRoot() { | |
| 460 | final TreeItem<String> root = getTreeView().getRoot(); | |
| 461 | ||
| 462 | return root instanceof VariableTreeItem ? | |
| 463 | (VariableTreeItem<String>) root : new VariableTreeItem<>( "root" ); | |
| 464 | } | |
| 465 | ||
| 466 | private ObservableList<TreeItem<String>> getSiblings( | |
| 467 | final TreeItem<String> item ) { | |
| 468 | final TreeItem<String> root = getTreeView().getRoot(); | |
| 469 | final TreeItem<String> parent = | |
| 470 | (item == null || item == root) ? root : item.getParent(); | |
| 471 | ||
| 472 | return parent.getChildren(); | |
| 473 | } | |
| 474 | ||
| 475 | private MultipleSelectionModel<TreeItem<String>> getSelectionModel() { | |
| 476 | return getTreeView().getSelectionModel(); | |
| 477 | } | |
| 478 | ||
| 479 | /** | |
| 480 | * Returns a copy of all the selected items. | |
| 481 | * | |
| 482 | * @return A list, possibly empty, containing all selected items in the | |
| 483 | * {@link TreeView}. | |
| 484 | */ | |
| 485 | private List<TreeItem<String>> getSelectedItems() { | |
| 486 | return new ArrayList<>( getSelectionModel().getSelectedItems() ); | |
| 487 | } | |
| 488 | ||
| 489 | public TreeItem<String> getSelectedItem() { | |
| 490 | final TreeItem<String> item = getSelectionModel().getSelectedItem(); | |
| 491 | return item == null ? getTreeView().getRoot() : item; | |
| 492 | } | |
| 493 | ||
| 494 | private Set<EventHandler<? super KeyEvent>> getKeyEventHandlers() { | |
| 495 | return mKeyEventHandlers; | |
| 496 | } | |
| 497 | } | |
| 1 | 498 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents behaviours for reading and writing string definitions. This | |
| 32 | * class cannot have any direct hooks into the user interface, as it defines | |
| 33 | * entry points into the definition data model loaded into an object | |
| 34 | * hierarchy. That hierarchy is converted to a UI model using an adapter | |
| 35 | * pattern. | |
| 36 | */ | |
| 37 | public interface DefinitionSource { | |
| 38 | ||
| 39 | /** | |
| 40 | * Creates an object capable of producing view-based objects from this | |
| 41 | * definition source. | |
| 42 | * | |
| 43 | * @return A hierarchical tree suitable for displaying in the definition pane. | |
| 44 | */ | |
| 45 | TreeAdapter getTreeAdapter(); | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the error message, if any, that occurred while loading the | |
| 49 | * definition source. | |
| 50 | * | |
| 51 | * @return The empty string if no error occurred, otherwise the error message. | |
| 52 | */ | |
| 53 | default String getError() { | |
| 54 | return ""; | |
| 55 | } | |
| 56 | } | |
| 1 | 57 |
| 1 | package com.scrivenvar.definition; | |
| 2 | ||
| 3 | /** | |
| 4 | * Responsible for parsing structured document formats. | |
| 5 | * | |
| 6 | * @param <T> The type of "node" for the document's object model. | |
| 7 | */ | |
| 8 | public interface DocumentParser<T> { | |
| 9 | ||
| 10 | /** | |
| 11 | * Parses a document into a nested object hierarchy. The object returned | |
| 12 | * from this call must be the root node in the document tree. | |
| 13 | * | |
| 14 | * @return The document's root node, which may be empty but never null. | |
| 15 | */ | |
| 16 | T getDocumentRoot(); | |
| 17 | } | |
| 1 | 18 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | /** | |
| 31 | * Used to find variable keys by matching values. The values are matched | |
| 32 | * according to the relationships provided in this enumeration. | |
| 33 | */ | |
| 34 | public enum FindMode { | |
| 35 | EXACT, | |
| 36 | CONTAINS, | |
| 37 | STARTS_WITH, | |
| 38 | LEVENSHTEIN | |
| 39 | } | |
| 40 | ||
| 1 | 41 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | import java.util.regex.Matcher; | |
| 32 | import java.util.regex.Pattern; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for performing string interpolation on key/value pairs stored | |
| 36 | * in a map. The values in the map can use a delimited syntax to refer to | |
| 37 | * keys in the map. | |
| 38 | * | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class MapInterpolator { | |
| 42 | ||
| 43 | /** | |
| 44 | * Matches variables delimited by dollar symbols. | |
| 45 | */ | |
| 46 | private final static String REGEX = "(\\$.*?\\$)"; | |
| 47 | ||
| 48 | /** | |
| 49 | * Compiled regular expression for matching delimited references. | |
| 50 | */ | |
| 51 | private final static Pattern REGEX_PATTERN = Pattern.compile( REGEX ); | |
| 52 | ||
| 53 | private final static int GROUP_DELIMITED = 1; | |
| 54 | ||
| 55 | /** | |
| 56 | * Empty. | |
| 57 | */ | |
| 58 | private MapInterpolator() { | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Performs string interpolation on the values in the given map. This will | |
| 63 | * change any value in the map that contains a variable that matches | |
| 64 | * {@link #REGEX_PATTERN}. | |
| 65 | * | |
| 66 | * @param map Contains values that represent references to keys. | |
| 67 | */ | |
| 68 | public static void interpolate( final Map<String, String> map ) { | |
| 69 | map.replaceAll( ( k, v ) -> resolve( map, v ) ); | |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Given a value with zero or more key references, this will resolve all | |
| 74 | * the values, recursively. If a key cannot be dereferenced, the value will | |
| 75 | * contain the key name. | |
| 76 | * | |
| 77 | * @param map Map to search for keys when resolving key references. | |
| 78 | * @param value Value containing zero or more key references | |
| 79 | * @return The given value with all embedded key references interpolated. | |
| 80 | */ | |
| 81 | private static String resolve( | |
| 82 | final Map<String, String> map, String value ) { | |
| 83 | final Matcher matcher = REGEX_PATTERN.matcher( value ); | |
| 84 | ||
| 85 | while( matcher.find() ) { | |
| 86 | final String keyName = matcher.group( GROUP_DELIMITED ); | |
| 87 | final String keyValue = resolve( | |
| 88 | map, map.getOrDefault( keyName, keyName ) | |
| 89 | ); | |
| 90 | ||
| 91 | value = value.replace( keyName, keyValue ); | |
| 92 | } | |
| 93 | ||
| 94 | return value; | |
| 95 | } | |
| 96 | } | |
| 1 | 97 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | import javafx.scene.control.TreeView; | |
| 32 | ||
| 33 | /** | |
| 34 | * Indicates that this is the top-most {@link TreeItem}. This class allows | |
| 35 | * the {@link TreeItemAdapter} to ignore the topmost definition. Such | |
| 36 | * contortions are necessary because {@link TreeView} requires a root item | |
| 37 | * that isn't part of the user's definition file. | |
| 38 | * <p> | |
| 39 | * Another approach would be to associate object pairs per {@link TreeItem}, | |
| 40 | * but that would be a waste of memory since the only "exception" case is | |
| 41 | * the root {@link TreeItem}. | |
| 42 | * </p> | |
| 43 | * | |
| 44 | * @param <T> The type of {@link TreeItem} to store in the {@link TreeView}. | |
| 45 | * @author White Magic Software, Ltd. | |
| 46 | */ | |
| 47 | public class RootTreeItem<T> extends VariableTreeItem<T> { | |
| 48 | /** | |
| 49 | * Default constructor, calls the superclass, no other behaviour. | |
| 50 | * | |
| 51 | * @param value The {@link TreeItem} node name to construct the superclass. | |
| 52 | * @see TreeItemAdapter#toMap(TreeItem) for details on how this | |
| 53 | * class is used. | |
| 54 | */ | |
| 55 | public RootTreeItem( final T value ) { | |
| 56 | super( value ); | |
| 57 | } | |
| 58 | } | |
| 1 | 59 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | ||
| 32 | import java.io.IOException; | |
| 33 | import java.nio.file.Path; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for converting an object hierarchy into a {@link TreeItem} | |
| 37 | * hierarchy. | |
| 38 | * | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public interface TreeAdapter { | |
| 42 | /** | |
| 43 | * Adapts the document produced by the given parser into a {@link TreeItem} | |
| 44 | * object that can be presented to the user within a GUI. | |
| 45 | * | |
| 46 | * @param root The default root node name. | |
| 47 | * @return The parsed document in a {@link TreeItem} that can be displayed | |
| 48 | * in a panel. | |
| 49 | */ | |
| 50 | TreeItem<String> adapt( String root ); | |
| 51 | ||
| 52 | /** | |
| 53 | * Exports the given root node to the given path. | |
| 54 | * | |
| 55 | * @param root The root node to export. | |
| 56 | * @param path Where to persist the data. | |
| 57 | * @throws IOException Could not write the data to the given path. | |
| 58 | */ | |
| 59 | void export( TreeItem<String> root, Path path ) throws IOException; | |
| 60 | } | |
| 1 | 61 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.scrivenvar.decorators.YamlVariableDecorator; | |
| 32 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 33 | import javafx.scene.control.TreeItem; | |
| 34 | import javafx.scene.control.TreeView; | |
| 35 | ||
| 36 | import java.util.HashMap; | |
| 37 | import java.util.Iterator; | |
| 38 | import java.util.Map; | |
| 39 | import java.util.Stack; | |
| 40 | ||
| 41 | import static com.scrivenvar.Constants.DEFAULT_MAP_SIZE; | |
| 42 | ||
| 43 | /** | |
| 44 | * Given a {@link TreeItem}, this will generate a flat map with all the | |
| 45 | * values in the tree recursively interpolated. The application integrates | |
| 46 | * definition files as follows: | |
| 47 | * <ol> | |
| 48 | * <li>Load YAML file into {@link JsonNode} hierarchy.</li> | |
| 49 | * <li>Convert JsonNode to a {@link TreeItem} hierarchy.</li> | |
| 50 | * <li>Interpolate {@link TreeItem} hierarchy as a flat map.</li> | |
| 51 | * <li>Substitute flat map variables into document as required.</li> | |
| 52 | * </ol> | |
| 53 | * | |
| 54 | * <p> | |
| 55 | * This class is responsible for producing the interpolated flat map. This | |
| 56 | * allows dynamic edits of the {@link TreeView} to be displayed in the | |
| 57 | * {@link HTMLPreviewPane} without having to reload the definition file. | |
| 58 | * Reloading the definition file would work, but has a number of drawbacks. | |
| 59 | * </p> | |
| 60 | * | |
| 61 | * @author White Magic Software, Ltd. | |
| 62 | */ | |
| 63 | public class TreeItemAdapter { | |
| 64 | /** | |
| 65 | * Separates YAML variable nodes (e.g., the dots in {@code $root.node.var$}). | |
| 66 | */ | |
| 67 | public static final String SEPARATOR = "."; | |
| 68 | ||
| 69 | /** | |
| 70 | * Default buffer length for keys ({@link StringBuilder} has 16 character | |
| 71 | * buffer) that should be large enough for most keys to avoid reallocating | |
| 72 | * memory to increase the {@link StringBuilder}'s buffer. | |
| 73 | */ | |
| 74 | public static final int DEFAULT_KEY_LENGTH = 64; | |
| 75 | ||
| 76 | /** | |
| 77 | * In-order traversal of a {@link TreeItem} hierarchy, exposing each item | |
| 78 | * as a consecutive list. | |
| 79 | */ | |
| 80 | private static final class TreeIterator | |
| 81 | implements Iterator<TreeItem<String>> { | |
| 82 | private final Stack<TreeItem<String>> mStack = new Stack<>(); | |
| 83 | ||
| 84 | public TreeIterator( final TreeItem<String> root ) { | |
| 85 | if( root != null ) { | |
| 86 | mStack.push( root ); | |
| 87 | } | |
| 88 | } | |
| 89 | ||
| 90 | @Override | |
| 91 | public boolean hasNext() { | |
| 92 | return !mStack.isEmpty(); | |
| 93 | } | |
| 94 | ||
| 95 | @Override | |
| 96 | public TreeItem<String> next() { | |
| 97 | final TreeItem<String> next = mStack.pop(); | |
| 98 | next.getChildren().forEach( mStack::push ); | |
| 99 | ||
| 100 | return next; | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | private TreeItemAdapter() { | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Iterate over a given root node (at any level of the tree) and process each | |
| 109 | * leaf node into a flat map. Values must be interpolated separately. | |
| 110 | */ | |
| 111 | public static Map<String, String> toMap( final TreeItem<String> root ) { | |
| 112 | final Map<String, String> map = new HashMap<>( DEFAULT_MAP_SIZE ); | |
| 113 | final TreeIterator iterator = new TreeIterator( root ); | |
| 114 | ||
| 115 | iterator.forEachRemaining( item -> { | |
| 116 | if( item.isLeaf() ) { | |
| 117 | map.put( toPath( item.getParent() ), item.getValue() ); | |
| 118 | } | |
| 119 | } ); | |
| 120 | ||
| 121 | return map; | |
| 122 | } | |
| 123 | ||
| 124 | ||
| 125 | /** | |
| 126 | * For a given node, this will ascend the tree to generate a key name | |
| 127 | * that is associated with the leaf node's value. | |
| 128 | * | |
| 129 | * @param node Ascendants represent the key to this node's value. | |
| 130 | * @param <T> Data type that the {@link TreeItem} contains. | |
| 131 | * @return The string representation of the node's unique key. | |
| 132 | */ | |
| 133 | public static <T> String toPath( TreeItem<T> node ) { | |
| 134 | assert node != null; | |
| 135 | ||
| 136 | final StringBuilder key = new StringBuilder( DEFAULT_KEY_LENGTH ); | |
| 137 | final Stack<TreeItem<T>> stack = new Stack<>(); | |
| 138 | ||
| 139 | while( node != null && !(node instanceof RootTreeItem) ) { | |
| 140 | stack.push( node ); | |
| 141 | node = node.getParent(); | |
| 142 | } | |
| 143 | ||
| 144 | // Gets set at end of first iteration (to avoid an if condition). | |
| 145 | String separator = ""; | |
| 146 | ||
| 147 | while( !stack.empty() ) { | |
| 148 | final T subkey = stack.pop().getValue(); | |
| 149 | key.append( separator ); | |
| 150 | key.append( subkey ); | |
| 151 | separator = SEPARATOR; | |
| 152 | } | |
| 153 | ||
| 154 | return YamlVariableDecorator.entoken( key.toString() ); | |
| 155 | } | |
| 156 | } | |
| 1 | 157 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | ||
| 32 | import java.text.Normalizer; | |
| 33 | import java.util.Stack; | |
| 34 | ||
| 35 | import static com.scrivenvar.definition.FindMode.*; | |
| 36 | import static java.text.Normalizer.Form.NFD; | |
| 37 | ||
| 38 | /** | |
| 39 | * Provides behaviour afforded to variable names and their corresponding value. | |
| 40 | * | |
| 41 | * @param <T> The type of TreeItem (usually String). | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class VariableTreeItem<T> extends TreeItem<T> { | |
| 45 | ||
| 46 | /** | |
| 47 | * Constructs a new item with a default value. | |
| 48 | * | |
| 49 | * @param value Passed up to superclass. | |
| 50 | */ | |
| 51 | public VariableTreeItem( final T value ) { | |
| 52 | super( value ); | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * Finds a leaf starting at the current node with text that matches the given | |
| 57 | * value. | |
| 58 | * | |
| 59 | * @param text The text to match against each leaf in the tree. | |
| 60 | * @return The leaf that has a value starting with the given text. | |
| 61 | */ | |
| 62 | public VariableTreeItem<T> findLeaf( final String text ) { | |
| 63 | return findLeaf( text, STARTS_WITH ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Finds a leaf starting at the current node with text that matches the given | |
| 68 | * value. | |
| 69 | * | |
| 70 | * @param text The text to match against each leaf in the tree. | |
| 71 | * @param findMode What algorithm is used to match the given text. | |
| 72 | * @return The leaf that has a value starting with the given text. | |
| 73 | */ | |
| 74 | public VariableTreeItem<T> findLeaf( | |
| 75 | final String text, final FindMode findMode ) { | |
| 76 | final Stack<VariableTreeItem<T>> stack = new Stack<>(); | |
| 77 | final VariableTreeItem<T> root = this; | |
| 78 | ||
| 79 | stack.push( root ); | |
| 80 | ||
| 81 | // Don't try to find keys for blank/empty variable values. | |
| 82 | boolean found = text.isBlank(); | |
| 83 | VariableTreeItem<T> node = null; | |
| 84 | ||
| 85 | while( !found && !stack.isEmpty() ) { | |
| 86 | node = stack.pop(); | |
| 87 | ||
| 88 | if( findMode == EXACT && node.valueEquals( text ) ) { | |
| 89 | found = true; | |
| 90 | } | |
| 91 | else if( findMode == CONTAINS && node.valueContains( text ) ) { | |
| 92 | found = true; | |
| 93 | } | |
| 94 | else if( findMode == STARTS_WITH && node.valueStartsWith( text ) ) { | |
| 95 | found = true; | |
| 96 | } | |
| 97 | else { | |
| 98 | for( final TreeItem<T> child : node.getChildren() ) { | |
| 99 | stack.push( (VariableTreeItem<T>) child ); | |
| 100 | } | |
| 101 | ||
| 102 | // No match found, yet. | |
| 103 | node = null; | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | return node; | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Returns the value of the string without diacritic marks. | |
| 112 | * | |
| 113 | * @return A non-null, possibly empty string. | |
| 114 | */ | |
| 115 | private String getDiacriticlessValue() { | |
| 116 | final String value = getValue().toString(); | |
| 117 | final String normalized = Normalizer.normalize( value, NFD ); | |
| 118 | ||
| 119 | return normalized.replaceAll( "\\p{M}", "" ); | |
| 120 | } | |
| 121 | ||
| 122 | /** | |
| 123 | * Returns true if this node is a leaf and its value starts with the given | |
| 124 | * text. | |
| 125 | * | |
| 126 | * @param s The text to compare against the node value. | |
| 127 | * @return true Node is a leaf and its value starts with the given value. | |
| 128 | */ | |
| 129 | private boolean valueStartsWith( final String s ) { | |
| 130 | return isLeaf() && getDiacriticlessValue().startsWith( s ); | |
| 131 | } | |
| 132 | ||
| 133 | /** | |
| 134 | * Returns true if this node is a leaf and its value contains the given text. | |
| 135 | * | |
| 136 | * @param s The text to compare against the node value. | |
| 137 | * @return true Node is a leaf and its value contains the given value. | |
| 138 | */ | |
| 139 | private boolean valueContains( final String s ) { | |
| 140 | return isLeaf() && getDiacriticlessValue().contains( s ); | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Returns true if this node is a leaf and its value equals the given text. | |
| 145 | * | |
| 146 | * @param s The text to compare against the node value. | |
| 147 | * @return true Node is a leaf and its value equals the given value. | |
| 148 | */ | |
| 149 | private boolean valueEquals( final String s ) { | |
| 150 | return isLeaf() && getValue().equals( s ); | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Returns the path for this node, with nodes made distinct using the | |
| 155 | * separator character. This uses two loops: one for pushing nodes onto a | |
| 156 | * stack and one for popping them off to create the path in desired order. | |
| 157 | * | |
| 158 | * @return A non-null string, possibly empty. | |
| 159 | */ | |
| 160 | public String toPath() { | |
| 161 | return TreeItemAdapter.toPath( getParent() ); | |
| 162 | } | |
| 163 | } | |
| 1 | 164 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.scrivenvar.definition.DefinitionSource; | |
| 31 | import com.scrivenvar.definition.TreeAdapter; | |
| 32 | ||
| 33 | import java.nio.file.Path; | |
| 34 | ||
| 35 | /** | |
| 36 | * Represents a definition data source for YAML files. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class YamlDefinitionSource implements DefinitionSource { | |
| 41 | ||
| 42 | private final YamlTreeAdapter mYamlTreeAdapter; | |
| 43 | ||
| 44 | /** | |
| 45 | * Constructs a new YAML definition source, populated from the given file. | |
| 46 | * | |
| 47 | * @param path Path to the YAML definition file. | |
| 48 | */ | |
| 49 | public YamlDefinitionSource( final Path path ) { | |
| 50 | assert path != null; | |
| 51 | ||
| 52 | mYamlTreeAdapter = new YamlTreeAdapter( path ); | |
| 53 | } | |
| 54 | ||
| 55 | @Override | |
| 56 | public TreeAdapter getTreeAdapter() { | |
| 57 | return mYamlTreeAdapter; | |
| 58 | } | |
| 59 | ||
| 60 | @Override | |
| 61 | public String getError() { | |
| 62 | return getYamlParser().getError(); | |
| 63 | } | |
| 64 | ||
| 65 | private YamlParser getYamlParser() { | |
| 66 | return mYamlTreeAdapter.getYamlParser(); | |
| 67 | } | |
| 68 | } | |
| 1 | 69 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 32 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |
| 33 | import com.scrivenvar.Messages; | |
| 34 | import com.scrivenvar.definition.DocumentParser; | |
| 35 | ||
| 36 | import java.io.InputStream; | |
| 37 | import java.nio.file.Files; | |
| 38 | import java.nio.file.Path; | |
| 39 | ||
| 40 | import static com.scrivenvar.Constants.STATUS_BAR_OK; | |
| 41 | ||
| 42 | /** | |
| 43 | * Responsible for reading a YAML document into an object hierarchy. | |
| 44 | * | |
| 45 | * @author White Magic Software, Ltd. | |
| 46 | */ | |
| 47 | public class YamlParser implements DocumentParser<JsonNode> { | |
| 48 | ||
| 49 | /** | |
| 50 | * Error that occurred while parsing. | |
| 51 | */ | |
| 52 | private String mError; | |
| 53 | ||
| 54 | /** | |
| 55 | * Start of the Universe (the YAML document node that contains all others). | |
| 56 | */ | |
| 57 | private final JsonNode mDocumentRoot; | |
| 58 | ||
| 59 | /** | |
| 60 | * Creates a new YamlParser instance that attempts to parse the contents | |
| 61 | * of the YAML document given from a path. In the event that the file either | |
| 62 | * does not exist or is empty, a fake | |
| 63 | * | |
| 64 | * @param path Path to a file containing YAML data to parse. | |
| 65 | */ | |
| 66 | public YamlParser( final Path path ) { | |
| 67 | assert path != null; | |
| 68 | mDocumentRoot = parse( path ); | |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Returns the parent node for the entire YAML document tree. | |
| 73 | * | |
| 74 | * @return The document root, never {@code null}. | |
| 75 | */ | |
| 76 | @Override | |
| 77 | public JsonNode getDocumentRoot() { | |
| 78 | return mDocumentRoot; | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Parses the given path containing YAML data into an object hierarchy. | |
| 83 | * | |
| 84 | * @param path {@link Path} to the YAML resource to parse. | |
| 85 | * @return The parsed contents, or an empty object hierarchy. | |
| 86 | */ | |
| 87 | private JsonNode parse( final Path path ) { | |
| 88 | try( final InputStream in = Files.newInputStream( path ) ) { | |
| 89 | setError( Messages.get( STATUS_BAR_OK ) ); | |
| 90 | ||
| 91 | return new ObjectMapper( new YAMLFactory() ).readTree( in ); | |
| 92 | } catch( final Exception e ) { | |
| 93 | setError( Messages.get( "yaml.error.open" ) ); | |
| 94 | ||
| 95 | // Ensure that a document root node exists by relying on the | |
| 96 | // default failure condition when processing. This is required | |
| 97 | // because the input stream could not be read. | |
| 98 | return new ObjectMapper().createObjectNode(); | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | private void setError( final String error ) { | |
| 103 | mError = error; | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Returns the last error message, if any, that occurred during parsing. | |
| 108 | * | |
| 109 | * @return The error message or the empty string if no error occurred. | |
| 110 | */ | |
| 111 | public String getError() { | |
| 112 | return mError; | |
| 113 | } | |
| 114 | } | |
| 1 | 115 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.fasterxml.jackson.databind.node.ObjectNode; | |
| 32 | import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; | |
| 33 | import com.scrivenvar.definition.RootTreeItem; | |
| 34 | import com.scrivenvar.definition.TreeAdapter; | |
| 35 | import com.scrivenvar.definition.VariableTreeItem; | |
| 36 | import javafx.scene.control.TreeItem; | |
| 37 | import javafx.scene.control.TreeView; | |
| 38 | ||
| 39 | import java.io.IOException; | |
| 40 | import java.nio.file.Path; | |
| 41 | import java.util.Map.Entry; | |
| 42 | ||
| 43 | /** | |
| 44 | * Transforms a JsonNode hierarchy into a tree that can be displayed in a user | |
| 45 | * interface and vice-versa. | |
| 46 | * | |
| 47 | * @author White Magic Software, Ltd. | |
| 48 | */ | |
| 49 | public class YamlTreeAdapter implements TreeAdapter { | |
| 50 | private final YamlParser mParser; | |
| 51 | ||
| 52 | /** | |
| 53 | * Constructs a new instance that will use the given path to read | |
| 54 | * the object hierarchy from a data source. | |
| 55 | * | |
| 56 | * @param path Path to YAML contents to parse. | |
| 57 | */ | |
| 58 | public YamlTreeAdapter( final Path path ) { | |
| 59 | mParser = new YamlParser( path ); | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public void export( final TreeItem<String> treeItem, final Path path ) | |
| 64 | throws IOException { | |
| 65 | final YAMLMapper mapper = new YAMLMapper(); | |
| 66 | final ObjectNode root = mapper.createObjectNode(); | |
| 67 | ||
| 68 | // Iterate over the root item's children. The root item is used by the | |
| 69 | // application to ensure definitions can always be added to a tree, as | |
| 70 | // such it is not meant to be exported, only its children. | |
| 71 | for( final TreeItem<String> child : treeItem.getChildren() ) { | |
| 72 | export( child, root ); | |
| 73 | } | |
| 74 | ||
| 75 | // Writes as UTF8 by default. | |
| 76 | mapper.writeValue( path.toFile(), root ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Recursive method to generate an object hierarchy that represents the | |
| 81 | * given {@link TreeItem} hierarchy. | |
| 82 | * | |
| 83 | * @param item The {@link TreeItem} to reproduce as an object hierarchy. | |
| 84 | * @param node The {@link ObjectNode} to update to reflect the | |
| 85 | * {@link TreeItem} hierarchy. | |
| 86 | */ | |
| 87 | private void export( final TreeItem<String> item, ObjectNode node ) { | |
| 88 | final var children = item.getChildren(); | |
| 89 | ||
| 90 | // If the current item has more than one non-leaf child, it's an | |
| 91 | // object node and must become a new nested object. | |
| 92 | if( !(children.size() == 1 && children.get( 0 ).isLeaf()) ) { | |
| 93 | node = node.putObject( item.getValue() ); | |
| 94 | } | |
| 95 | ||
| 96 | for( final TreeItem<String> child : children ) { | |
| 97 | if( child.isLeaf() ) { | |
| 98 | node.put( item.getValue(), child.getValue() ); | |
| 99 | } | |
| 100 | else { | |
| 101 | export( child, node ); | |
| 102 | } | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Converts a YAML document to a {@link TreeItem} based on the document | |
| 108 | * keys. Only the first document in the stream is adapted. | |
| 109 | * | |
| 110 | * @param root Root {@link TreeItem} node name. | |
| 111 | * @return A {@link TreeItem} populated with all the keys in the YAML | |
| 112 | * document. | |
| 113 | */ | |
| 114 | public TreeItem<String> adapt( final String root ) { | |
| 115 | final JsonNode rootNode = getYamlParser().getDocumentRoot(); | |
| 116 | final TreeItem<String> rootItem = createRootTreeItem( root ); | |
| 117 | ||
| 118 | rootItem.setExpanded( true ); | |
| 119 | adapt( rootNode, rootItem ); | |
| 120 | return rootItem; | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Iterate over a given root node (at any level of the tree) and adapt each | |
| 125 | * leaf node. | |
| 126 | * | |
| 127 | * @param rootNode A JSON node (YAML node) to adapt. | |
| 128 | * @param rootItem The tree item to use as the root when processing the node. | |
| 129 | */ | |
| 130 | private void adapt( | |
| 131 | final JsonNode rootNode, final TreeItem<String> rootItem ) { | |
| 132 | rootNode.fields().forEachRemaining( | |
| 133 | ( Entry<String, JsonNode> leaf ) -> adapt( leaf, rootItem ) | |
| 134 | ); | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Recursively adapt each rootNode to a corresponding rootItem. | |
| 139 | * | |
| 140 | * @param rootNode The node to adapt. | |
| 141 | * @param rootItem The item to adapt using the node's key. | |
| 142 | */ | |
| 143 | private void adapt( | |
| 144 | final Entry<String, JsonNode> rootNode, | |
| 145 | final TreeItem<String> rootItem ) { | |
| 146 | final JsonNode leafNode = rootNode.getValue(); | |
| 147 | final String key = rootNode.getKey(); | |
| 148 | final TreeItem<String> leaf = createTreeItem( key ); | |
| 149 | ||
| 150 | if( leafNode.isValueNode() ) { | |
| 151 | leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) ); | |
| 152 | } | |
| 153 | ||
| 154 | rootItem.getChildren().add( leaf ); | |
| 155 | ||
| 156 | if( leafNode.isObject() ) { | |
| 157 | adapt( leafNode, leaf ); | |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * Creates a new {@link TreeItem} that can be added to the {@link TreeView}. | |
| 163 | * | |
| 164 | * @param value The node's value. | |
| 165 | * @return A new {@link TreeItem}, never {@code null}. | |
| 166 | */ | |
| 167 | private TreeItem<String> createTreeItem( final String value ) { | |
| 168 | return new VariableTreeItem<>( value ); | |
| 169 | } | |
| 170 | ||
| 171 | /** | |
| 172 | * Creates a new {@link TreeItem} that is intended to be the root-level item | |
| 173 | * added to the {@link TreeView}. This allows the root item to be | |
| 174 | * distinguished from the other items so that reference keys do not include | |
| 175 | * "Definition" as part of their name. | |
| 176 | * | |
| 177 | * @param value The node's value. | |
| 178 | * @return A new {@link TreeItem}, never {@code null}. | |
| 179 | */ | |
| 180 | private TreeItem<String> createRootTreeItem( final String value ) { | |
| 181 | return new RootTreeItem<>( value ); | |
| 182 | } | |
| 183 | ||
| 184 | public YamlParser getYamlParser() { | |
| 185 | return mParser; | |
| 186 | } | |
| 187 | } | |
| 1 | 188 |
| 1 | /* | |
| 2 | * Copyright 2017 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.dialogs; | |
| 29 | ||
| 30 | import static com.scrivenvar.Messages.get; | |
| 31 | import com.scrivenvar.service.events.impl.ButtonOrderPane; | |
| 32 | import static javafx.scene.control.ButtonType.CANCEL; | |
| 33 | import static javafx.scene.control.ButtonType.OK; | |
| 34 | import javafx.scene.control.Dialog; | |
| 35 | import javafx.stage.Window; | |
| 36 | ||
| 37 | /** | |
| 38 | * Superclass that abstracts common behaviours for all dialogs. | |
| 39 | * | |
| 40 | * @author White Magic Software, Ltd. | |
| 41 | * @param <T> The type of dialog to create (usually String). | |
| 42 | */ | |
| 43 | public abstract class AbstractDialog<T> extends Dialog<T> { | |
| 44 | ||
| 45 | /** | |
| 46 | * Ensures that all dialogs can be closed. | |
| 47 | * | |
| 48 | * @param owner The parent window of this dialog. | |
| 49 | * @param title The messages title to display in the title bar. | |
| 50 | */ | |
| 51 | @SuppressWarnings( "OverridableMethodCallInConstructor" ) | |
| 52 | public AbstractDialog( final Window owner, final String title ) { | |
| 53 | setTitle( get( title ) ); | |
| 54 | setResizable( true ); | |
| 55 | ||
| 56 | initOwner( owner ); | |
| 57 | initCloseAction(); | |
| 58 | initDialogPane(); | |
| 59 | initDialogButtons(); | |
| 60 | initComponents(); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Initialize the component layout. | |
| 65 | */ | |
| 66 | protected abstract void initComponents(); | |
| 67 | ||
| 68 | /** | |
| 69 | * Set the dialog to use a button order pane with an OK and a CANCEL button. | |
| 70 | */ | |
| 71 | protected void initDialogPane() { | |
| 72 | setDialogPane( new ButtonOrderPane() ); | |
| 73 | } | |
| 74 | ||
| 75 | /** | |
| 76 | * Set an OK and CANCEL button on the dialog. | |
| 77 | */ | |
| 78 | protected void initDialogButtons() { | |
| 79 | getDialogPane().getButtonTypes().addAll( OK, CANCEL ); | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Attaches a setOnCloseRequest to the dialog's [X] button so that the user | |
| 84 | * can always close the window, even if there's an error. | |
| 85 | */ | |
| 86 | protected final void initCloseAction() { | |
| 87 | final Window window = getDialogPane().getScene().getWindow(); | |
| 88 | window.setOnCloseRequest( event -> window.hide() ); | |
| 89 | } | |
| 90 | } | |
| 1 | 91 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.dialogs; | |
| 28 | ||
| 29 | import static com.scrivenvar.Messages.get; | |
| 30 | import com.scrivenvar.controls.BrowseFileButton; | |
| 31 | import com.scrivenvar.controls.EscapeTextField; | |
| 32 | import java.nio.file.Path; | |
| 33 | import javafx.application.Platform; | |
| 34 | import javafx.beans.binding.Bindings; | |
| 35 | import javafx.beans.property.SimpleStringProperty; | |
| 36 | import javafx.beans.property.StringProperty; | |
| 37 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 38 | import static javafx.scene.control.ButtonType.OK; | |
| 39 | import javafx.scene.control.DialogPane; | |
| 40 | import javafx.scene.control.Label; | |
| 41 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 42 | import javafx.stage.Window; | |
| 43 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a markdown image. | |
| 47 | * | |
| 48 | * @author Karl Tauber | |
| 49 | */ | |
| 50 | public class ImageDialog extends AbstractDialog<String> { | |
| 51 | ||
| 52 | private final StringProperty image = new SimpleStringProperty(); | |
| 53 | ||
| 54 | public ImageDialog( final Window owner, final Path basePath ) { | |
| 55 | super(owner, "Dialog.image.title" ); | |
| 56 | ||
| 57 | final DialogPane dialogPane = getDialogPane(); | |
| 58 | dialogPane.setContent( pane ); | |
| 59 | ||
| 60 | linkBrowseFileButton.setBasePath( basePath ); | |
| 61 | linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( get( "Dialog.image.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) ); | |
| 62 | linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() ); | |
| 63 | ||
| 64 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 65 | urlField.escapedTextProperty().isEmpty() | |
| 66 | .or( textField.escapedTextProperty().isEmpty() ) ); | |
| 67 | ||
| 68 | image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 69 | .then( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 70 | .otherwise( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) ); | |
| 71 | previewField.textProperty().bind( image ); | |
| 72 | ||
| 73 | setResultConverter( dialogButton -> { | |
| 74 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 75 | return (data == ButtonData.OK_DONE) ? image.get() : null; | |
| 76 | } ); | |
| 77 | ||
| 78 | Platform.runLater( () -> { | |
| 79 | urlField.requestFocus(); | |
| 80 | ||
| 81 | if( urlField.getText().startsWith( "http://" ) ) { | |
| 82 | urlField.selectRange( "http://".length(), urlField.getLength() ); | |
| 83 | } | |
| 84 | } ); | |
| 85 | } | |
| 86 | ||
| 87 | @Override | |
| 88 | protected void initComponents() { | |
| 89 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 90 | pane = new MigPane(); | |
| 91 | Label urlLabel = new Label(); | |
| 92 | urlField = new EscapeTextField(); | |
| 93 | linkBrowseFileButton = new BrowseFileButton(); | |
| 94 | Label textLabel = new Label(); | |
| 95 | textField = new EscapeTextField(); | |
| 96 | Label titleLabel = new Label(); | |
| 97 | titleField = new EscapeTextField(); | |
| 98 | Label previewLabel = new Label(); | |
| 99 | previewField = new Label(); | |
| 100 | ||
| 101 | //======== pane ======== | |
| 102 | { | |
| 103 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" ); | |
| 104 | pane.setRows( "[][][][]" ); | |
| 105 | ||
| 106 | //---- urlLabel ---- | |
| 107 | urlLabel.setText( get( "Dialog.image.urlLabel.text" ) ); | |
| 108 | pane.add( urlLabel, "cell 0 0" ); | |
| 109 | ||
| 110 | //---- urlField ---- | |
| 111 | urlField.setEscapeCharacters( "()" ); | |
| 112 | urlField.setText( "http://yourlink.com" ); | |
| 113 | urlField.setPromptText( "http://yourlink.com" ); | |
| 114 | pane.add( urlField, "cell 1 0" ); | |
| 115 | pane.add( linkBrowseFileButton, "cell 2 0" ); | |
| 116 | ||
| 117 | //---- textLabel ---- | |
| 118 | textLabel.setText( get( "Dialog.image.textLabel.text" ) ); | |
| 119 | pane.add( textLabel, "cell 0 1" ); | |
| 120 | ||
| 121 | //---- textField ---- | |
| 122 | textField.setEscapeCharacters( "[]" ); | |
| 123 | pane.add( textField, "cell 1 1 2 1" ); | |
| 124 | ||
| 125 | //---- titleLabel ---- | |
| 126 | titleLabel.setText( get( "Dialog.image.titleLabel.text" ) ); | |
| 127 | pane.add( titleLabel, "cell 0 2" ); | |
| 128 | pane.add( titleField, "cell 1 2 2 1" ); | |
| 129 | ||
| 130 | //---- previewLabel ---- | |
| 131 | previewLabel.setText( get( "Dialog.image.previewLabel.text" ) ); | |
| 132 | pane.add( previewLabel, "cell 0 3" ); | |
| 133 | pane.add( previewField, "cell 1 3 2 1" ); | |
| 134 | } | |
| 135 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 136 | } | |
| 137 | ||
| 138 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 139 | private MigPane pane; | |
| 140 | private EscapeTextField urlField; | |
| 141 | private BrowseFileButton linkBrowseFileButton; | |
| 142 | private EscapeTextField textField; | |
| 143 | private EscapeTextField titleField; | |
| 144 | private Label previewField; | |
| 145 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 146 | } | |
| 1 | 147 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "ImageDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "ImageDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 34 | name: "linkBrowseFileButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 39 | name: "textLabel" | |
| 40 | "text": new FormMessage( null, "ImageDialog.textLabel.text" ) | |
| 41 | auxiliary() { | |
| 42 | "JavaCodeGenerator.variableLocal": true | |
| 43 | } | |
| 44 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 45 | "value": "cell 0 1" | |
| 46 | } ) | |
| 47 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 48 | name: "textField" | |
| 49 | "escapeCharacters": "[]" | |
| 50 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 51 | "value": "cell 1 1 2 1" | |
| 52 | } ) | |
| 53 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 54 | name: "titleLabel" | |
| 55 | "text": new FormMessage( null, "ImageDialog.titleLabel.text" ) | |
| 56 | auxiliary() { | |
| 57 | "JavaCodeGenerator.variableLocal": true | |
| 58 | } | |
| 59 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 60 | "value": "cell 0 2" | |
| 61 | } ) | |
| 62 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 63 | name: "titleField" | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 1 2 2 1" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 68 | name: "previewLabel" | |
| 69 | "text": new FormMessage( null, "ImageDialog.previewLabel.text" ) | |
| 70 | auxiliary() { | |
| 71 | "JavaCodeGenerator.variableLocal": true | |
| 72 | } | |
| 73 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 74 | "value": "cell 0 3" | |
| 75 | } ) | |
| 76 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 77 | name: "previewField" | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 1 3 2 1" | |
| 80 | } ) | |
| 81 | }, new FormLayoutConstraints( null ) { | |
| 82 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 83 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 84 | } ) | |
| 85 | } | |
| 86 | } | |
| 1 | 87 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.dialogs; | |
| 29 | ||
| 30 | import com.scrivenvar.controls.EscapeTextField; | |
| 31 | import com.scrivenvar.editors.markdown.HyperlinkModel; | |
| 32 | import javafx.application.Platform; | |
| 33 | import javafx.beans.binding.Bindings; | |
| 34 | import javafx.beans.property.SimpleStringProperty; | |
| 35 | import javafx.beans.property.StringProperty; | |
| 36 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 37 | import javafx.scene.control.DialogPane; | |
| 38 | import javafx.scene.control.Label; | |
| 39 | import javafx.stage.Window; | |
| 40 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 41 | ||
| 42 | import static com.scrivenvar.Messages.get; | |
| 43 | import static javafx.scene.control.ButtonType.OK; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a markdown link. | |
| 47 | * | |
| 48 | * @author Karl Tauber | |
| 49 | */ | |
| 50 | public class LinkDialog extends AbstractDialog<String> { | |
| 51 | ||
| 52 | private final StringProperty link = new SimpleStringProperty(); | |
| 53 | ||
| 54 | public LinkDialog( | |
| 55 | final Window owner, final HyperlinkModel hyperlink ) { | |
| 56 | super( owner, "Dialog.link.title" ); | |
| 57 | ||
| 58 | final DialogPane dialogPane = getDialogPane(); | |
| 59 | dialogPane.setContent( pane ); | |
| 60 | ||
| 61 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 62 | urlField.escapedTextProperty().isEmpty() ); | |
| 63 | ||
| 64 | textField.setText( hyperlink.getText() ); | |
| 65 | urlField.setText( hyperlink.getUrl() ); | |
| 66 | titleField.setText( hyperlink.getTitle() ); | |
| 67 | ||
| 68 | link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 69 | .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 70 | .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() ) | |
| 71 | .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) | |
| 72 | .otherwise( urlField.escapedTextProperty() ) ) ); | |
| 73 | ||
| 74 | setResultConverter( dialogButton -> { | |
| 75 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 76 | return (data == ButtonData.OK_DONE) ? link.get() : null; | |
| 77 | } ); | |
| 78 | ||
| 79 | Platform.runLater( () -> { | |
| 80 | urlField.requestFocus(); | |
| 81 | urlField.selectRange( 0, urlField.getLength() ); | |
| 82 | } ); | |
| 83 | } | |
| 84 | ||
| 85 | @Override | |
| 86 | protected void initComponents() { | |
| 87 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 88 | pane = new MigPane(); | |
| 89 | Label urlLabel = new Label(); | |
| 90 | urlField = new EscapeTextField(); | |
| 91 | Label textLabel = new Label(); | |
| 92 | textField = new EscapeTextField(); | |
| 93 | Label titleLabel = new Label(); | |
| 94 | titleField = new EscapeTextField(); | |
| 95 | ||
| 96 | //======== pane ======== | |
| 97 | { | |
| 98 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" ); | |
| 99 | pane.setRows( "[][][][]" ); | |
| 100 | ||
| 101 | //---- urlLabel ---- | |
| 102 | urlLabel.setText( get( "Dialog.link.urlLabel.text" ) ); | |
| 103 | pane.add( urlLabel, "cell 0 0" ); | |
| 104 | ||
| 105 | //---- urlField ---- | |
| 106 | urlField.setEscapeCharacters( "()" ); | |
| 107 | pane.add( urlField, "cell 1 0" ); | |
| 108 | ||
| 109 | //---- textLabel ---- | |
| 110 | textLabel.setText( get( "Dialog.link.textLabel.text" ) ); | |
| 111 | pane.add( textLabel, "cell 0 1" ); | |
| 112 | ||
| 113 | //---- textField ---- | |
| 114 | textField.setEscapeCharacters( "[]" ); | |
| 115 | pane.add( textField, "cell 1 1 3 1" ); | |
| 116 | ||
| 117 | //---- titleLabel ---- | |
| 118 | titleLabel.setText( get( "Dialog.link.titleLabel.text" ) ); | |
| 119 | pane.add( titleLabel, "cell 0 2" ); | |
| 120 | pane.add( titleField, "cell 1 2 3 1" ); | |
| 121 | } | |
| 122 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 123 | } | |
| 124 | ||
| 125 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 126 | private MigPane pane; | |
| 127 | private EscapeTextField urlField; | |
| 128 | private EscapeTextField textField; | |
| 129 | private EscapeTextField titleField; | |
| 130 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 131 | } | |
| 1 | 132 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "LinkDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "LinkDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseDirectoryButton" ) { | |
| 34 | name: "linkBrowseDirectoyButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 39 | name: "linkBrowseFileButton" | |
| 40 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 41 | "value": "cell 3 0" | |
| 42 | } ) | |
| 43 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 44 | name: "textLabel" | |
| 45 | "text": new FormMessage( null, "LinkDialog.textLabel.text" ) | |
| 46 | auxiliary() { | |
| 47 | "JavaCodeGenerator.variableLocal": true | |
| 48 | } | |
| 49 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 50 | "value": "cell 0 1" | |
| 51 | } ) | |
| 52 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 53 | name: "textField" | |
| 54 | "escapeCharacters": "[]" | |
| 55 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 56 | "value": "cell 1 1 3 1" | |
| 57 | } ) | |
| 58 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 59 | name: "titleLabel" | |
| 60 | "text": new FormMessage( null, "LinkDialog.titleLabel.text" ) | |
| 61 | auxiliary() { | |
| 62 | "JavaCodeGenerator.variableLocal": true | |
| 63 | } | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 0 2" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 68 | name: "titleField" | |
| 69 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 70 | "value": "cell 1 2 3 1" | |
| 71 | } ) | |
| 72 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 73 | name: "previewLabel" | |
| 74 | "text": new FormMessage( null, "LinkDialog.previewLabel.text" ) | |
| 75 | auxiliary() { | |
| 76 | "JavaCodeGenerator.variableLocal": true | |
| 77 | } | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 0 3" | |
| 80 | } ) | |
| 81 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 82 | name: "previewField" | |
| 83 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 84 | "value": "cell 1 3 3 1" | |
| 85 | } ) | |
| 86 | }, new FormLayoutConstraints( null ) { | |
| 87 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 88 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 89 | } ) | |
| 90 | } | |
| 91 | } | |
| 1 | 92 |
| 1 | /* | |
| 2 | * Copyright 2017 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.dialogs; | |
| 29 | ||
| 30 | import javafx.application.Platform; | |
| 31 | import javafx.geometry.Insets; | |
| 32 | import javafx.scene.control.Label; | |
| 33 | import javafx.scene.control.TextArea; | |
| 34 | import javafx.scene.layout.GridPane; | |
| 35 | import javafx.stage.Window; | |
| 36 | ||
| 37 | import static com.scrivenvar.Messages.get; | |
| 38 | import static javafx.scene.control.ButtonType.OK; | |
| 39 | ||
| 40 | /** | |
| 41 | * Responsible for managing the R startup script that is run when an R source | |
| 42 | * file is loaded. | |
| 43 | * | |
| 44 | * @author White Magic Software, Ltd. | |
| 45 | */ | |
| 46 | public class RScriptDialog extends AbstractDialog<String> { | |
| 47 | ||
| 48 | private TextArea mScriptArea; | |
| 49 | private final String mOriginalText; | |
| 50 | ||
| 51 | public RScriptDialog( | |
| 52 | final Window parent, final String title, final String script ) { | |
| 53 | super( parent, title ); | |
| 54 | mOriginalText = script; | |
| 55 | getScriptArea().setText( script ); | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | protected void initComponents() { | |
| 60 | final GridPane grid = new GridPane(); | |
| 61 | grid.setHgap( 10 ); | |
| 62 | grid.setVgap( 10 ); | |
| 63 | grid.setPadding( new Insets( 10, 10, 10, 10 ) ); | |
| 64 | ||
| 65 | final Label label = new Label( get( "Dialog.r.script.content" ) ); | |
| 66 | ||
| 67 | final TextArea textArea = getScriptArea(); | |
| 68 | textArea.setEditable( true ); | |
| 69 | textArea.setWrapText( true ); | |
| 70 | ||
| 71 | grid.add( label, 0, 0 ); | |
| 72 | grid.add( textArea, 0, 1 ); | |
| 73 | ||
| 74 | getDialogPane().setContent( grid ); | |
| 75 | ||
| 76 | Platform.runLater( textArea::requestFocus ); | |
| 77 | ||
| 78 | setResultConverter( | |
| 79 | dialogButton -> dialogButton == OK ? | |
| 80 | textArea.getText() : | |
| 81 | getOriginalText() | |
| 82 | ); | |
| 83 | } | |
| 84 | ||
| 85 | private TextArea getScriptArea() { | |
| 86 | if( mScriptArea == null ) { | |
| 87 | mScriptArea = new TextArea(); | |
| 88 | } | |
| 89 | ||
| 90 | return mScriptArea; | |
| 91 | } | |
| 92 | ||
| 93 | private String getOriginalText() { | |
| 94 | return mOriginalText; | |
| 95 | } | |
| 96 | } | |
| 1 | 97 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractPane; | |
| 31 | import javafx.application.Platform; | |
| 32 | import javafx.beans.property.ObjectProperty; | |
| 33 | import javafx.beans.property.SimpleObjectProperty; | |
| 34 | import javafx.beans.value.ChangeListener; | |
| 35 | import javafx.event.Event; | |
| 36 | import javafx.scene.control.ScrollPane; | |
| 37 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 38 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 39 | import org.fxmisc.undo.UndoManager; | |
| 40 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 41 | import org.fxmisc.wellbehaved.event.Nodes; | |
| 42 | ||
| 43 | import java.nio.file.Path; | |
| 44 | import java.util.function.Consumer; | |
| 45 | ||
| 46 | import static org.fxmisc.wellbehaved.event.InputMap.consume; | |
| 47 | ||
| 48 | /** | |
| 49 | * Represents common editing features for various types of text editors. | |
| 50 | * | |
| 51 | * @author White Magic Software, Ltd. | |
| 52 | */ | |
| 53 | public class EditorPane extends AbstractPane { | |
| 54 | ||
| 55 | private final StyleClassedTextArea mEditor = | |
| 56 | new StyleClassedTextArea( false ); | |
| 57 | private final VirtualizedScrollPane<StyleClassedTextArea> mScrollPane = | |
| 58 | new VirtualizedScrollPane<>( mEditor ); | |
| 59 | private final ObjectProperty<Path> mPath = new SimpleObjectProperty<>(); | |
| 60 | ||
| 61 | public EditorPane() { | |
| 62 | getScrollPane().setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS ); | |
| 63 | } | |
| 64 | ||
| 65 | @Override | |
| 66 | public void requestFocus() { | |
| 67 | Platform.runLater( () -> getEditor().requestFocus() ); | |
| 68 | } | |
| 69 | ||
| 70 | public void undo() { | |
| 71 | getUndoManager().undo(); | |
| 72 | } | |
| 73 | ||
| 74 | public void redo() { | |
| 75 | getUndoManager().redo(); | |
| 76 | } | |
| 77 | ||
| 78 | public UndoManager<?> getUndoManager() { | |
| 79 | return getEditor().getUndoManager(); | |
| 80 | } | |
| 81 | ||
| 82 | public String getText() { | |
| 83 | return getEditor().getText(); | |
| 84 | } | |
| 85 | ||
| 86 | public void setText( final String text ) { | |
| 87 | final var editor = getEditor(); | |
| 88 | editor.deselect(); | |
| 89 | editor.replaceText( text ); | |
| 90 | getUndoManager().mark(); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Call to hook into changes to the text area. | |
| 95 | * | |
| 96 | * @param listener Receives editor text change events. | |
| 97 | */ | |
| 98 | public void addTextChangeListener( | |
| 99 | final ChangeListener<? super String> listener ) { | |
| 100 | getEditor().textProperty().addListener( listener ); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Call to listen for when the caret moves to another paragraph. | |
| 105 | * | |
| 106 | * @param listener Receives paragraph change events. | |
| 107 | */ | |
| 108 | public void addCaretParagraphListener( | |
| 109 | final ChangeListener<? super Integer> listener ) { | |
| 110 | getEditor().currentParagraphProperty().addListener( listener ); | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * This method adds listeners to editor events. | |
| 115 | * | |
| 116 | * @param <T> The event type. | |
| 117 | * @param <U> The consumer type for the given event type. | |
| 118 | * @param event The event of interest. | |
| 119 | * @param consumer The method to call when the event happens. | |
| 120 | */ | |
| 121 | public <T extends Event, U extends T> void addKeyboardListener( | |
| 122 | final EventPattern<? super T, ? extends U> event, | |
| 123 | final Consumer<? super U> consumer ) { | |
| 124 | Nodes.addInputMap( getEditor(), consume( event, consumer ) ); | |
| 125 | } | |
| 126 | ||
| 127 | /** | |
| 128 | * Repositions the cursor and scroll bar to the top of the file. | |
| 129 | */ | |
| 130 | public void scrollToTop() { | |
| 131 | getEditor().moveTo( 0 ); | |
| 132 | getScrollPane().scrollYToPixel( 0 ); | |
| 133 | } | |
| 134 | ||
| 135 | public StyleClassedTextArea getEditor() { | |
| 136 | return mEditor; | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Returns the scroll pane that contains the text area. | |
| 141 | * | |
| 142 | * @return The scroll pane that contains the content to edit. | |
| 143 | */ | |
| 144 | public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() { | |
| 145 | return mScrollPane; | |
| 146 | } | |
| 147 | ||
| 148 | public Path getPath() { | |
| 149 | return mPath.get(); | |
| 150 | } | |
| 151 | ||
| 152 | public void setPath( final Path path ) { | |
| 153 | mPath.set( path ); | |
| 154 | } | |
| 155 | } | |
| 1 | 156 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.decorators.RVariableDecorator; | |
| 32 | import com.scrivenvar.decorators.VariableDecorator; | |
| 33 | import com.scrivenvar.decorators.YamlVariableDecorator; | |
| 34 | import java.nio.file.Path; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for creating a variable name decorator suited to a particular | |
| 38 | * file type. | |
| 39 | * | |
| 40 | * @author White Magic Software, Ltd. | |
| 41 | */ | |
| 42 | public class VariableNameDecoratorFactory extends AbstractFileFactory { | |
| 43 | ||
| 44 | private VariableNameDecoratorFactory() { | |
| 45 | } | |
| 46 | ||
| 47 | public static VariableDecorator newInstance( final Path path ) { | |
| 48 | final var factory = new VariableNameDecoratorFactory(); | |
| 49 | final VariableDecorator result; | |
| 50 | ||
| 51 | switch( factory.lookup( path ) ) { | |
| 52 | case RMARKDOWN: | |
| 53 | case RXML: | |
| 54 | result = new RVariableDecorator(); | |
| 55 | break; | |
| 56 | ||
| 57 | default: | |
| 58 | result = new YamlVariableDecorator(); | |
| 59 | break; | |
| 60 | } | |
| 61 | ||
| 62 | return result; | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.FileEditorTab; | |
| 31 | import com.scrivenvar.decorators.VariableDecorator; | |
| 32 | import com.scrivenvar.definition.DefinitionPane; | |
| 33 | import com.scrivenvar.definition.FindMode; | |
| 34 | import com.scrivenvar.definition.VariableTreeItem; | |
| 35 | import javafx.event.Event; | |
| 36 | import javafx.scene.control.TreeItem; | |
| 37 | import javafx.scene.input.KeyEvent; | |
| 38 | import org.fxmisc.richtext.StyledTextArea; | |
| 39 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 40 | ||
| 41 | import java.nio.file.Path; | |
| 42 | import java.text.BreakIterator; | |
| 43 | import java.util.function.Consumer; | |
| 44 | ||
| 45 | import static com.scrivenvar.definition.FindMode.*; | |
| 46 | import static javafx.scene.input.KeyCode.SPACE; | |
| 47 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 48 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 49 | ||
| 50 | /** | |
| 51 | * Provides the logic for injecting variable names within the editor. | |
| 52 | * | |
| 53 | * @author White Magic Software, Ltd. | |
| 54 | */ | |
| 55 | public final class VariableNameInjector { | |
| 56 | ||
| 57 | /** | |
| 58 | * Recipient of name injections. | |
| 59 | */ | |
| 60 | private FileEditorTab mTab; | |
| 61 | ||
| 62 | /** | |
| 63 | * Initiates double-click events. | |
| 64 | */ | |
| 65 | private DefinitionPane mDefinitionPane; | |
| 66 | ||
| 67 | /** | |
| 68 | * Initializes the variable name injector against the given pane. | |
| 69 | * | |
| 70 | * @param tab The tab to inject variable names into. | |
| 71 | * @param pane The definition panel to listen to for double-click events. | |
| 72 | */ | |
| 73 | public VariableNameInjector( | |
| 74 | final FileEditorTab tab, final DefinitionPane pane ) { | |
| 75 | setFileEditorTab( tab ); | |
| 76 | setDefinitionPane( pane ); | |
| 77 | initKeyboardEventListeners(); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Trap control+space and the @ key. | |
| 82 | * | |
| 83 | * @param tab The file editor that sends keyboard events for variable name | |
| 84 | * injection. | |
| 85 | */ | |
| 86 | public void initKeyboardEventListeners( final FileEditorTab tab ) { | |
| 87 | setFileEditorTab( tab ); | |
| 88 | initKeyboardEventListeners(); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Inserts the variable | |
| 93 | */ | |
| 94 | public void injectSelectedItem() { | |
| 95 | final TreeItem<String> item = getDefinitionPane().getSelectedItem(); | |
| 96 | ||
| 97 | if( item.isLeaf() ) { | |
| 98 | // This avoids a direct typecast. | |
| 99 | final VariableTreeItem<String> leaf = getDefinitionPane().findLeaf( | |
| 100 | item.getValue(), FindMode.EXACT ); | |
| 101 | final StyledTextArea<?, ?> editor = getEditor(); | |
| 102 | ||
| 103 | editor.insertText( editor.getCaretPosition(), decorate( leaf ) ); | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Traps Control+SPACE to auto-insert definition key names. | |
| 109 | */ | |
| 110 | private void initKeyboardEventListeners() { | |
| 111 | addKeyboardListener( | |
| 112 | keyPressed( SPACE, CONTROL_DOWN ), | |
| 113 | this::autoinsert | |
| 114 | ); | |
| 115 | } | |
| 116 | ||
| 117 | /** | |
| 118 | * Pressing Control+SPACE will find a node that matches the current word and | |
| 119 | * substitute the YAML variable reference. | |
| 120 | * | |
| 121 | * @param e Ignored -- it can only be Control+SPACE. | |
| 122 | */ | |
| 123 | private void autoinsert( final KeyEvent e ) { | |
| 124 | final String paragraph = getCaretParagraph(); | |
| 125 | final int[] boundaries = getWordBoundariesAtCaret(); | |
| 126 | final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] ); | |
| 127 | final VariableTreeItem<String> leaf = findLeaf( word ); | |
| 128 | ||
| 129 | if( leaf != null ) { | |
| 130 | replaceText( boundaries[ 0 ], boundaries[ 1 ], decorate( leaf ) ); | |
| 131 | expand( leaf ); | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | private int[] getWordBoundariesAtCaret() { | |
| 136 | final String paragraph = getCaretParagraph(); | |
| 137 | int offset = getCurrentCaretColumn(); | |
| 138 | ||
| 139 | final BreakIterator wordBreaks = BreakIterator.getWordInstance(); | |
| 140 | wordBreaks.setText( paragraph ); | |
| 141 | ||
| 142 | // Scan back until the first word is found. | |
| 143 | while( offset > 0 && wordBreaks.isBoundary( offset ) ) { | |
| 144 | offset--; | |
| 145 | } | |
| 146 | ||
| 147 | final int[] boundaries = new int[ 2 ]; | |
| 148 | boundaries[ 1 ] = wordBreaks.following( offset ); | |
| 149 | boundaries[ 0 ] = wordBreaks.previous(); | |
| 150 | ||
| 151 | return boundaries; | |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * Decorates a {@link TreeItem} using the syntax specific to the type of | |
| 156 | * document being edited. | |
| 157 | * | |
| 158 | * @param leaf The path to the leaf (the definition key) to be decorated. | |
| 159 | */ | |
| 160 | private String decorate( final VariableTreeItem<String> leaf ) { | |
| 161 | return decorate( leaf.toPath() ); | |
| 162 | } | |
| 163 | ||
| 164 | /** | |
| 165 | * Decorates a variable using the syntax specific to the type of document | |
| 166 | * being edited. | |
| 167 | * | |
| 168 | * @param variable The variable to decorate in dot-notation without any | |
| 169 | * start or end sigils present. | |
| 170 | */ | |
| 171 | private String decorate( final String variable ) { | |
| 172 | return getVariableDecorator().decorate( variable ); | |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Updates the text at the given position within the current paragraph. | |
| 177 | * | |
| 178 | * @param posBegan The starting index in the paragraph text to replace. | |
| 179 | * @param posEnded The ending index in the paragraph text to replace. | |
| 180 | * @param text Overwrite the paragraph substring with this text. | |
| 181 | */ | |
| 182 | private void replaceText( | |
| 183 | final int posBegan, final int posEnded, final String text ) { | |
| 184 | final int p = getCurrentParagraph(); | |
| 185 | ||
| 186 | getEditor().replaceText( p, posBegan, p, posEnded, text ); | |
| 187 | } | |
| 188 | ||
| 189 | /** | |
| 190 | * Returns the caret's current paragraph position. | |
| 191 | * | |
| 192 | * @return A number greater than or equal to 0. | |
| 193 | */ | |
| 194 | private int getCurrentParagraph() { | |
| 195 | return getEditor().getCurrentParagraph(); | |
| 196 | } | |
| 197 | ||
| 198 | /** | |
| 199 | * Returns the text for the paragraph that contains the caret. | |
| 200 | * | |
| 201 | * @return A non-null string, possibly empty. | |
| 202 | */ | |
| 203 | private String getCaretParagraph() { | |
| 204 | return getEditor().getText( getCurrentParagraph() ); | |
| 205 | } | |
| 206 | ||
| 207 | /** | |
| 208 | * Returns the caret position within the current paragraph. | |
| 209 | * | |
| 210 | * @return A value from 0 to the length of the current paragraph. | |
| 211 | */ | |
| 212 | private int getCurrentCaretColumn() { | |
| 213 | return getEditor().getCaretColumn(); | |
| 214 | } | |
| 215 | ||
| 216 | private VariableTreeItem<String> findLeaf( final String word ) { | |
| 217 | assert word != null; | |
| 218 | ||
| 219 | VariableTreeItem<String> leaf = findLeafExact( word ); | |
| 220 | ||
| 221 | leaf = leaf == null ? findLeafStartsWith( word ) : leaf; | |
| 222 | leaf = leaf == null ? findLeafContains( word ) : leaf; | |
| 223 | leaf = leaf == null ? findLeafLevenshtein( word ) : leaf; | |
| 224 | ||
| 225 | return leaf; | |
| 226 | } | |
| 227 | ||
| 228 | private VariableTreeItem<String> findLeafExact( final String text ) { | |
| 229 | return findLeaf( text, EXACT ); | |
| 230 | } | |
| 231 | ||
| 232 | private VariableTreeItem<String> findLeafContains( final String text ) { | |
| 233 | return findLeaf( text, CONTAINS ); | |
| 234 | } | |
| 235 | ||
| 236 | private VariableTreeItem<String> findLeafStartsWith( final String text ) { | |
| 237 | return findLeaf( text, STARTS_WITH ); | |
| 238 | } | |
| 239 | ||
| 240 | private VariableTreeItem<String> findLeafLevenshtein( final String text ) { | |
| 241 | return findLeaf( text, LEVENSHTEIN ); | |
| 242 | } | |
| 243 | ||
| 244 | /** | |
| 245 | * Finds the first leaf having a value that starts with the given text, or | |
| 246 | * contains the text if contains is true. | |
| 247 | * | |
| 248 | * @param text The text to find in the definition tree. | |
| 249 | * @param findMode Dictates what search criteria to use for matching words. | |
| 250 | * @return The leaf that starts with the given text, or null if not found. | |
| 251 | */ | |
| 252 | private VariableTreeItem<String> findLeaf( | |
| 253 | final String text, final FindMode findMode ) { | |
| 254 | return getDefinitionPane().findLeaf( text, findMode ); | |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 258 | * Collapses the tree then expands and selects the given node. | |
| 259 | * | |
| 260 | * @param node The node to expand. | |
| 261 | */ | |
| 262 | private void expand( final TreeItem<String> node ) { | |
| 263 | final DefinitionPane pane = getDefinitionPane(); | |
| 264 | pane.collapse(); | |
| 265 | pane.expand( node ); | |
| 266 | pane.select( node ); | |
| 267 | } | |
| 268 | ||
| 269 | /** | |
| 270 | * @return A variable decorator that corresponds to the given file type. | |
| 271 | */ | |
| 272 | private VariableDecorator getVariableDecorator() { | |
| 273 | return VariableNameDecoratorFactory.newInstance( getFilename() ); | |
| 274 | } | |
| 275 | ||
| 276 | private Path getFilename() { | |
| 277 | return getFileEditorTab().getPath(); | |
| 278 | } | |
| 279 | ||
| 280 | private EditorPane getEditorPane() { | |
| 281 | return getFileEditorTab().getEditorPane(); | |
| 282 | } | |
| 283 | ||
| 284 | /** | |
| 285 | * Delegates to the file editor pane, and, ultimately, to its text area. | |
| 286 | */ | |
| 287 | private <T extends Event, U extends T> void addKeyboardListener( | |
| 288 | final EventPattern<? super T, ? extends U> event, | |
| 289 | final Consumer<? super U> consumer ) { | |
| 290 | getEditorPane().addKeyboardListener( event, consumer ); | |
| 291 | } | |
| 292 | ||
| 293 | private StyledTextArea<?, ?> getEditor() { | |
| 294 | return getEditorPane().getEditor(); | |
| 295 | } | |
| 296 | ||
| 297 | public FileEditorTab getFileEditorTab() { | |
| 298 | return mTab; | |
| 299 | } | |
| 300 | ||
| 301 | public void setFileEditorTab( final FileEditorTab tab ) { | |
| 302 | mTab = tab; | |
| 303 | } | |
| 304 | ||
| 305 | private DefinitionPane getDefinitionPane() { | |
| 306 | return mDefinitionPane; | |
| 307 | } | |
| 308 | ||
| 309 | private void setDefinitionPane( final DefinitionPane definitionPane ) { | |
| 310 | mDefinitionPane = definitionPane; | |
| 311 | } | |
| 312 | } | |
| 1 | 313 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | ||
| 32 | /** | |
| 33 | * Represents the model for a hyperlink: text and url text. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class HyperlinkModel { | |
| 38 | ||
| 39 | private String text; | |
| 40 | private String url; | |
| 41 | private String title; | |
| 42 | ||
| 43 | /** | |
| 44 | * Constructs a new hyperlink model in Markdown format by default with no | |
| 45 | * title (i.e., tooltip). | |
| 46 | * | |
| 47 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 48 | * @param url The destination URL (e.g., when clicked). | |
| 49 | */ | |
| 50 | public HyperlinkModel( final String text, final String url ) { | |
| 51 | this( text, url, null ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Constructs a new hyperlink model for the given AST link. | |
| 56 | * | |
| 57 | * @param link A markdown link. | |
| 58 | */ | |
| 59 | public HyperlinkModel( final Link link ) { | |
| 60 | this( | |
| 61 | link.getText().toString(), | |
| 62 | link.getUrl().toString(), | |
| 63 | link.getTitle().toString() | |
| 64 | ); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Constructs a new hyperlink model in Markdown format by default. | |
| 69 | * | |
| 70 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 71 | * @param url The destination URL (e.g., when clicked). | |
| 72 | * @param title The hyperlink title (e.g., shown as a tooltip). | |
| 73 | */ | |
| 74 | public HyperlinkModel( final String text, final String url, final String title ) { | |
| 75 | setText( text ); | |
| 76 | setUrl( url ); | |
| 77 | setTitle( title ); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Returns the string in Markdown format by default. | |
| 82 | * | |
| 83 | * @return A markdown version of the hyperlink. | |
| 84 | */ | |
| 85 | @Override | |
| 86 | public String toString() { | |
| 87 | String format = "%s%s%s"; | |
| 88 | ||
| 89 | if( hasText() ) { | |
| 90 | format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)"); | |
| 91 | } | |
| 92 | ||
| 93 | // Becomes ""+URL+"" if no text is set. | |
| 94 | // Becomes [TITLE]+(URL)+"" if no title is set. | |
| 95 | // Becomes [TITLE]+(URL+ \"TITLE\") if title is set. | |
| 96 | return String.format( format, getText(), getUrl(), getTitle() ); | |
| 97 | } | |
| 98 | ||
| 99 | public final void setText( final String text ) { | |
| 100 | this.text = nullSafe( text ); | |
| 101 | } | |
| 102 | ||
| 103 | public final void setUrl( final String url ) { | |
| 104 | this.url = nullSafe( url ); | |
| 105 | } | |
| 106 | ||
| 107 | public final void setTitle( final String title ) { | |
| 108 | this.title = nullSafe( title ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Answers whether text has been set for the hyperlink. | |
| 113 | * | |
| 114 | * @return true This is a text link. | |
| 115 | */ | |
| 116 | public boolean hasText() { | |
| 117 | return !getText().isEmpty(); | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Answers whether a title (tooltip) has been set for the hyperlink. | |
| 122 | * | |
| 123 | * @return true There is a title. | |
| 124 | */ | |
| 125 | public boolean hasTitle() { | |
| 126 | return !getTitle().isEmpty(); | |
| 127 | } | |
| 128 | ||
| 129 | public String getText() { | |
| 130 | return this.text; | |
| 131 | } | |
| 132 | ||
| 133 | public String getUrl() { | |
| 134 | return this.url; | |
| 135 | } | |
| 136 | ||
| 137 | public String getTitle() { | |
| 138 | return this.title; | |
| 139 | } | |
| 140 | ||
| 141 | private String nullSafe( final String s ) { | |
| 142 | return s == null ? "" : s; | |
| 143 | } | |
| 144 | } | |
| 1 | 145 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | import com.vladsch.flexmark.util.ast.Node; | |
| 32 | import com.vladsch.flexmark.util.ast.NodeVisitor; | |
| 33 | import com.vladsch.flexmark.util.ast.VisitHandler; | |
| 34 | ||
| 35 | /** | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public class LinkVisitor { | |
| 39 | ||
| 40 | private NodeVisitor visitor; | |
| 41 | private Link link; | |
| 42 | private final int offset; | |
| 43 | ||
| 44 | /** | |
| 45 | * Creates a hyperlink given an offset into a paragraph and the markdown AST | |
| 46 | * link node. | |
| 47 | * | |
| 48 | * @param index Index into the paragraph that indicates the hyperlink to | |
| 49 | * change. | |
| 50 | */ | |
| 51 | public LinkVisitor( final int index ) { | |
| 52 | this.offset = index; | |
| 53 | } | |
| 54 | ||
| 55 | public Link process( final Node root ) { | |
| 56 | getVisitor().visit( root ); | |
| 57 | return getLink(); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * @param link Not null. | |
| 62 | */ | |
| 63 | private void visit( final Link link ) { | |
| 64 | final int began = link.getStartOffset(); | |
| 65 | final int ended = link.getEndOffset(); | |
| 66 | final int index = getOffset(); | |
| 67 | ||
| 68 | if( index >= began && index <= ended ) { | |
| 69 | setLink( link ); | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | private synchronized NodeVisitor getVisitor() { | |
| 74 | if( this.visitor == null ) { | |
| 75 | this.visitor = createVisitor(); | |
| 76 | } | |
| 77 | ||
| 78 | return this.visitor; | |
| 79 | } | |
| 80 | ||
| 81 | protected NodeVisitor createVisitor() { | |
| 82 | return new NodeVisitor( | |
| 83 | new VisitHandler<>( Link.class, LinkVisitor.this::visit ) ); | |
| 84 | } | |
| 85 | ||
| 86 | private Link getLink() { | |
| 87 | return this.link; | |
| 88 | } | |
| 89 | ||
| 90 | private void setLink( final Link link ) { | |
| 91 | this.link = link; | |
| 92 | } | |
| 93 | ||
| 94 | public int getOffset() { | |
| 95 | return this.offset; | |
| 96 | } | |
| 97 | } | |
| 1 | 98 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.dialogs.ImageDialog; | |
| 31 | import com.scrivenvar.dialogs.LinkDialog; | |
| 32 | import com.scrivenvar.editors.EditorPane; | |
| 33 | import com.scrivenvar.processors.markdown.MarkdownProcessor; | |
| 34 | import com.vladsch.flexmark.ast.Link; | |
| 35 | import com.vladsch.flexmark.util.ast.Node; | |
| 36 | import javafx.scene.control.Dialog; | |
| 37 | import javafx.scene.control.IndexRange; | |
| 38 | import javafx.scene.input.KeyEvent; | |
| 39 | import javafx.stage.Window; | |
| 40 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 41 | ||
| 42 | import java.nio.file.Path; | |
| 43 | import java.util.regex.Matcher; | |
| 44 | import java.util.regex.Pattern; | |
| 45 | ||
| 46 | import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN; | |
| 47 | import static com.scrivenvar.util.Utils.ltrim; | |
| 48 | import static com.scrivenvar.util.Utils.rtrim; | |
| 49 | import static javafx.scene.input.KeyCode.ENTER; | |
| 50 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 51 | ||
| 52 | /** | |
| 53 | * Markdown editor pane. | |
| 54 | * | |
| 55 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 56 | */ | |
| 57 | public class MarkdownEditorPane extends EditorPane { | |
| 58 | ||
| 59 | private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile( | |
| 60 | "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | |
| 61 | ||
| 62 | public MarkdownEditorPane() { | |
| 63 | initEditor(); | |
| 64 | } | |
| 65 | ||
| 66 | private void initEditor() { | |
| 67 | final StyleClassedTextArea textArea = getEditor(); | |
| 68 | ||
| 69 | textArea.setWrapText( true ); | |
| 70 | textArea.getStyleClass().add( "markdown-editor" ); | |
| 71 | textArea.getStylesheets().add( STYLESHEET_MARKDOWN ); | |
| 72 | ||
| 73 | addKeyboardListener( keyPressed( ENTER ), this::enterPressed ); | |
| 74 | } | |
| 75 | ||
| 76 | private void enterPressed( final KeyEvent e ) { | |
| 77 | final StyleClassedTextArea textArea = getEditor(); | |
| 78 | final String currentLine = | |
| 79 | textArea.getText( textArea.getCurrentParagraph() ); | |
| 80 | final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine ); | |
| 81 | ||
| 82 | String newText = "\n"; | |
| 83 | ||
| 84 | if( matcher.matches() ) { | |
| 85 | if( !matcher.group( 2 ).isEmpty() ) { | |
| 86 | // indent new line with same whitespace characters and list markers | |
| 87 | // as current line | |
| 88 | newText = newText.concat( matcher.group( 1 ) ); | |
| 89 | } | |
| 90 | else { | |
| 91 | // current line contains only whitespace characters and list markers | |
| 92 | // --> empty current line | |
| 93 | final int caretPosition = textArea.getCaretPosition(); | |
| 94 | textArea.selectRange( caretPosition - currentLine.length(), | |
| 95 | caretPosition ); | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | textArea.replaceSelection( newText ); | |
| 100 | ||
| 101 | // Ensure that the window scrolls when Enter is pressed at the bottom of | |
| 102 | // the pane. | |
| 103 | textArea.requestFollowCaret(); | |
| 104 | } | |
| 105 | ||
| 106 | public void surroundSelection( final String leading, final String trailing ) { | |
| 107 | surroundSelection( leading, trailing, null ); | |
| 108 | } | |
| 109 | ||
| 110 | public void surroundSelection( String leading, String trailing, | |
| 111 | final String hint ) { | |
| 112 | final StyleClassedTextArea textArea = getEditor(); | |
| 113 | ||
| 114 | // Note: not using textArea.insertText() to insert leading and trailing | |
| 115 | // because this would add two changes to undo history | |
| 116 | final IndexRange selection = textArea.getSelection(); | |
| 117 | int start = selection.getStart(); | |
| 118 | int end = selection.getEnd(); | |
| 119 | ||
| 120 | final String selectedText = textArea.getSelectedText(); | |
| 121 | ||
| 122 | // remove leading and trailing whitespaces from selected text | |
| 123 | String trimmedSelectedText = selectedText.trim(); | |
| 124 | if( trimmedSelectedText.length() < selectedText.length() ) { | |
| 125 | start += selectedText.indexOf( trimmedSelectedText ); | |
| 126 | end = start + trimmedSelectedText.length(); | |
| 127 | } | |
| 128 | ||
| 129 | // remove leading whitespaces from leading text if selection starts at zero | |
| 130 | if( start == 0 ) { | |
| 131 | leading = ltrim( leading ); | |
| 132 | } | |
| 133 | ||
| 134 | // remove trailing whitespaces from trailing text if selection ends at | |
| 135 | // text end | |
| 136 | if( end == textArea.getLength() ) { | |
| 137 | trailing = rtrim( trailing ); | |
| 138 | } | |
| 139 | ||
| 140 | // remove leading line separators from leading text | |
| 141 | // if there are line separators before the selected text | |
| 142 | if( leading.startsWith( "\n" ) ) { | |
| 143 | for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) { | |
| 144 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 145 | break; | |
| 146 | } | |
| 147 | leading = leading.substring( 1 ); | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | // remove trailing line separators from trailing or leading text | |
| 152 | // if there are line separators after the selected text | |
| 153 | final boolean trailingIsEmpty = trailing.isEmpty(); | |
| 154 | String str = trailingIsEmpty ? leading : trailing; | |
| 155 | ||
| 156 | if( str.endsWith( "\n" ) ) { | |
| 157 | final int length = textArea.getLength(); | |
| 158 | ||
| 159 | for( int i = end; i < length && str.endsWith( "\n" ); i++ ) { | |
| 160 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 161 | break; | |
| 162 | } | |
| 163 | ||
| 164 | str = str.substring( 0, str.length() - 1 ); | |
| 165 | } | |
| 166 | ||
| 167 | if( trailingIsEmpty ) { | |
| 168 | leading = str; | |
| 169 | } | |
| 170 | else { | |
| 171 | trailing = str; | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | int selStart = start + leading.length(); | |
| 176 | int selEnd = end + leading.length(); | |
| 177 | ||
| 178 | // insert hint text if selection is empty | |
| 179 | if( hint != null && trimmedSelectedText.isEmpty() ) { | |
| 180 | trimmedSelectedText = hint; | |
| 181 | selEnd = selStart + hint.length(); | |
| 182 | } | |
| 183 | ||
| 184 | // prevent undo merging with previous text entered by user | |
| 185 | getUndoManager().preventMerge(); | |
| 186 | ||
| 187 | // replace text and update selection | |
| 188 | textArea.replaceText( start, | |
| 189 | end, | |
| 190 | leading + trimmedSelectedText + trailing ); | |
| 191 | textArea.selectRange( selStart, selEnd ); | |
| 192 | } | |
| 193 | ||
| 194 | /** | |
| 195 | * Returns one of: selected text, word under cursor, or parsed hyperlink from | |
| 196 | * the markdown AST. | |
| 197 | * | |
| 198 | * @return An instance containing the link URL and display text. | |
| 199 | */ | |
| 200 | private HyperlinkModel getHyperlink() { | |
| 201 | final StyleClassedTextArea textArea = getEditor(); | |
| 202 | final String selectedText = textArea.getSelectedText(); | |
| 203 | ||
| 204 | // Get the current paragraph, convert to Markdown nodes. | |
| 205 | final MarkdownProcessor mp = new MarkdownProcessor( null ); | |
| 206 | final int p = textArea.getCurrentParagraph(); | |
| 207 | final String paragraph = textArea.getText( p ); | |
| 208 | final Node node = mp.toNode( paragraph ); | |
| 209 | final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() ); | |
| 210 | final Link link = visitor.process( node ); | |
| 211 | ||
| 212 | if( link != null ) { | |
| 213 | textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() ); | |
| 214 | } | |
| 215 | ||
| 216 | return createHyperlinkModel( | |
| 217 | link, selectedText, "https://localhost" | |
| 218 | ); | |
| 219 | } | |
| 220 | ||
| 221 | @SuppressWarnings("SameParameterValue") | |
| 222 | private HyperlinkModel createHyperlinkModel( | |
| 223 | final Link link, final String selection, final String url ) { | |
| 224 | ||
| 225 | return link == null | |
| 226 | ? new HyperlinkModel( selection, url ) | |
| 227 | : new HyperlinkModel( link ); | |
| 228 | } | |
| 229 | ||
| 230 | private Path getParentPath() { | |
| 231 | final Path path = getPath(); | |
| 232 | return (path != null) ? path.getParent() : null; | |
| 233 | } | |
| 234 | ||
| 235 | private Dialog<String> createLinkDialog() { | |
| 236 | return new LinkDialog( getWindow(), getHyperlink() ); | |
| 237 | } | |
| 238 | ||
| 239 | private Dialog<String> createImageDialog() { | |
| 240 | return new ImageDialog( getWindow(), getParentPath() ); | |
| 241 | } | |
| 242 | ||
| 243 | private void insertObject( final Dialog<String> dialog ) { | |
| 244 | dialog.showAndWait().ifPresent( | |
| 245 | result -> getEditor().replaceSelection( result ) | |
| 246 | ); | |
| 247 | } | |
| 248 | ||
| 249 | public void insertLink() { | |
| 250 | insertObject( createLinkDialog() ); | |
| 251 | } | |
| 252 | ||
| 253 | public void insertImage() { | |
| 254 | insertObject( createImageDialog() ); | |
| 255 | } | |
| 256 | ||
| 257 | private Window getWindow() { | |
| 258 | return getScrollPane().getScene().getWindow(); | |
| 259 | } | |
| 260 | } | |
| 1 | 261 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.predicates.files; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.FileSystems; | |
| 32 | import java.nio.file.PathMatcher; | |
| 33 | import java.util.Collection; | |
| 34 | import java.util.function.Predicate; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for testing whether a given path (to a file) matches one of the | |
| 38 | * filename extension patterns provided during construction. | |
| 39 | * | |
| 40 | * @author White Magic Software, Ltd. | |
| 41 | */ | |
| 42 | public class FileTypePredicate implements Predicate<File> { | |
| 43 | ||
| 44 | private final PathMatcher mMatcher; | |
| 45 | ||
| 46 | /** | |
| 47 | * Constructs a new instance given a set of file extension globs. | |
| 48 | * | |
| 49 | * @param patterns Comma-separated list of globbed extensions including the | |
| 50 | * Kleene star (e.g., <code>*.md,*.markdown,*.txt</code>). | |
| 51 | */ | |
| 52 | public FileTypePredicate( final String patterns ) { | |
| 53 | mMatcher = FileSystems.getDefault().getPathMatcher( | |
| 54 | "glob:**{" + patterns + "}" | |
| 55 | ); | |
| 56 | } | |
| 57 | ||
| 58 | /** | |
| 59 | * Constructs a new instance given a list of file extension globs, each must | |
| 60 | * include the Kleene star (a.k.a. asterisk). | |
| 61 | * | |
| 62 | * @param patterns Collection of globbed extensions. | |
| 63 | */ | |
| 64 | public FileTypePredicate( final Collection<String> patterns ) { | |
| 65 | this( String.join( ",", patterns ) ); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * Returns true if the file matches the patterns defined during construction. | |
| 70 | * | |
| 71 | * @param file The filename to match against the given glob patterns. | |
| 72 | * | |
| 73 | * @return false The filename does not match the glob patterns. | |
| 74 | */ | |
| 75 | @Override | |
| 76 | public boolean test( final File file ) { | |
| 77 | return getMatcher().matches( file.toPath() ); | |
| 78 | } | |
| 79 | ||
| 80 | private PathMatcher getMatcher() { | |
| 81 | return mMatcher; | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.predicates.strings; | |
| 29 | ||
| 30 | /** | |
| 31 | * Determines if one string contains another. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class ContainsPredicate extends StringPredicate { | |
| 36 | ||
| 37 | /** | |
| 38 | * Calls the superclass to construct the instance. | |
| 39 | * | |
| 40 | * @param comparate Not null. | |
| 41 | */ | |
| 42 | public ContainsPredicate( final String comparate ) { | |
| 43 | super( comparate ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Answers whether the given strings match each other. What match means will | |
| 48 | * depend on user preferences. The empty condition is required to return the | |
| 49 | * first node in a list of child nodes when the user has not yet selected a | |
| 50 | * node. | |
| 51 | * | |
| 52 | * @param comparator The string to compare against the comparate. | |
| 53 | * | |
| 54 | * @return true if s1 and s2 are a match according to some criteria,or s2 is | |
| 55 | * empty. | |
| 56 | */ | |
| 57 | @Override | |
| 58 | public boolean test( final String comparator ) { | |
| 59 | final String comparate = getComparate().toLowerCase(); | |
| 60 | return comparator.contains( comparate.toLowerCase() ) | |
| 61 | || comparate.isEmpty(); | |
| 62 | } | |
| 63 | } | |
| 1 | 64 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.predicates.strings; | |
| 29 | ||
| 30 | /** | |
| 31 | * Determines if a string starts with another. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class StartsPredicate extends StringPredicate { | |
| 36 | ||
| 37 | /** | |
| 38 | * Constructs a new instance using a comparate that will be compared with | |
| 39 | * the comparator during the test. | |
| 40 | * | |
| 41 | * @param comparate The string to compare against the comparator. | |
| 42 | */ | |
| 43 | public StartsPredicate( final String comparate ) { | |
| 44 | super( comparate ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Compares two strings. | |
| 49 | * | |
| 50 | * @param comparator A non-null string, possibly empty. | |
| 51 | * | |
| 52 | * @return true The comparator starts with the comparate, ignoring case. | |
| 53 | */ | |
| 54 | @Override | |
| 55 | public boolean test( final String comparator ) { | |
| 56 | final String comparate = getComparate().toLowerCase(); | |
| 57 | return comparator.startsWith( comparate.toLowerCase() ); | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.predicates.strings; | |
| 29 | ||
| 30 | import java.util.function.Predicate; | |
| 31 | ||
| 32 | /** | |
| 33 | * General predicate for different types of string comparisons. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public abstract class StringPredicate implements Predicate<String> { | |
| 38 | ||
| 39 | private final String comparate; | |
| 40 | ||
| 41 | public StringPredicate( final String comparate ) { | |
| 42 | this.comparate = comparate; | |
| 43 | } | |
| 44 | ||
| 45 | protected String getComparate() { | |
| 46 | return this.comparate; | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 1 | /* | |
| 2 | * Copyright 2016 David Croft and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | ||
| 33 | import java.io.File; | |
| 34 | import java.io.FileInputStream; | |
| 35 | import java.io.FileOutputStream; | |
| 36 | import java.util.*; | |
| 37 | import java.util.prefs.AbstractPreferences; | |
| 38 | import java.util.prefs.BackingStoreException; | |
| 39 | ||
| 40 | /** | |
| 41 | * Preferences implementation that stores to a user-defined file. Local file | |
| 42 | * storage is preferred over a certain operating system's monolithic trash heap | |
| 43 | * called a registry. When the OS is locked down, the default Preferences | |
| 44 | * implementation will try to write to the registry and fail due to permissions | |
| 45 | * problems. This class sidesteps the issue entirely by writing to the user's | |
| 46 | * home directory, where permissions should be a bit more lax. | |
| 47 | */ | |
| 48 | public class FilePreferences extends AbstractPreferences { | |
| 49 | private final Notifier mNotifier = Services.load( Notifier.class ); | |
| 50 | ||
| 51 | private final Map<String, String> mRoot = new TreeMap<>(); | |
| 52 | private final Map<String, FilePreferences> mChildren = new TreeMap<>(); | |
| 53 | private boolean mRemoved; | |
| 54 | ||
| 55 | private final Object mMutex = new Object(); | |
| 56 | ||
| 57 | public FilePreferences( | |
| 58 | final AbstractPreferences parent, final String name ) { | |
| 59 | super( parent, name ); | |
| 60 | ||
| 61 | try { | |
| 62 | sync(); | |
| 63 | } catch( final BackingStoreException ex ) { | |
| 64 | error( ex ); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | protected void putSpi( final String key, final String value ) { | |
| 70 | mRoot.put( key, value ); | |
| 71 | ||
| 72 | try { | |
| 73 | flush(); | |
| 74 | } catch( final BackingStoreException ex ) { | |
| 75 | error( ex ); | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | protected String getSpi( final String key ) { | |
| 81 | return mRoot.get( key ); | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | protected void removeSpi( final String key ) { | |
| 86 | mRoot.remove( key ); | |
| 87 | ||
| 88 | try { | |
| 89 | flush(); | |
| 90 | } catch( final BackingStoreException ex ) { | |
| 91 | error( ex ); | |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | @Override | |
| 96 | protected void removeNodeSpi() throws BackingStoreException { | |
| 97 | mRemoved = true; | |
| 98 | flush(); | |
| 99 | } | |
| 100 | ||
| 101 | @Override | |
| 102 | protected String[] keysSpi() { | |
| 103 | return mRoot.keySet().toArray( new String[ 0 ] ); | |
| 104 | } | |
| 105 | ||
| 106 | @Override | |
| 107 | protected String[] childrenNamesSpi() { | |
| 108 | return mChildren.keySet().toArray( new String[ 0 ] ); | |
| 109 | } | |
| 110 | ||
| 111 | @Override | |
| 112 | protected FilePreferences childSpi( final String name ) { | |
| 113 | FilePreferences child = mChildren.get( name ); | |
| 114 | ||
| 115 | if( child == null || child.isRemoved() ) { | |
| 116 | child = new FilePreferences( this, name ); | |
| 117 | mChildren.put( name, child ); | |
| 118 | } | |
| 119 | ||
| 120 | return child; | |
| 121 | } | |
| 122 | ||
| 123 | @Override | |
| 124 | protected void syncSpi() { | |
| 125 | if( isRemoved() ) { | |
| 126 | return; | |
| 127 | } | |
| 128 | ||
| 129 | final File file = FilePreferencesFactory.getPreferencesFile(); | |
| 130 | ||
| 131 | if( !file.exists() ) { | |
| 132 | return; | |
| 133 | } | |
| 134 | ||
| 135 | synchronized( mMutex ) { | |
| 136 | final Properties p = new Properties(); | |
| 137 | ||
| 138 | try { | |
| 139 | p.load( new FileInputStream( file ) ); | |
| 140 | ||
| 141 | final String path = getPath(); | |
| 142 | final Enumeration<?> propertyNames = p.propertyNames(); | |
| 143 | ||
| 144 | while( propertyNames.hasMoreElements() ) { | |
| 145 | final String propKey = (String) propertyNames.nextElement(); | |
| 146 | ||
| 147 | if( propKey.startsWith( path ) ) { | |
| 148 | final String subKey = propKey.substring( path.length() ); | |
| 149 | ||
| 150 | // Only load immediate descendants | |
| 151 | if( subKey.indexOf( '.' ) == -1 ) { | |
| 152 | mRoot.put( subKey, p.getProperty( propKey ) ); | |
| 153 | } | |
| 154 | } | |
| 155 | } | |
| 156 | } catch( final Exception ex ) { | |
| 157 | error( new BackingStoreException( ex ) ); | |
| 158 | } | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | private String getPath() { | |
| 163 | final FilePreferences parent = (FilePreferences) parent(); | |
| 164 | ||
| 165 | return parent == null ? "" : parent.getPath() + name() + '.'; | |
| 166 | } | |
| 167 | ||
| 168 | @Override | |
| 169 | protected void flushSpi() { | |
| 170 | final File file = FilePreferencesFactory.getPreferencesFile(); | |
| 171 | ||
| 172 | synchronized( mMutex ) { | |
| 173 | final Properties p = new Properties(); | |
| 174 | ||
| 175 | try { | |
| 176 | final String path = getPath(); | |
| 177 | ||
| 178 | if( file.exists() ) { | |
| 179 | p.load( new FileInputStream( file ) ); | |
| 180 | ||
| 181 | final List<String> toRemove = new ArrayList<>(); | |
| 182 | ||
| 183 | // Make a list of all direct children of this node to be removed | |
| 184 | final Enumeration<?> propertyNames = p.propertyNames(); | |
| 185 | ||
| 186 | while( propertyNames.hasMoreElements() ) { | |
| 187 | final String propKey = (String) propertyNames.nextElement(); | |
| 188 | if( propKey.startsWith( path ) ) { | |
| 189 | final String subKey = propKey.substring( path.length() ); | |
| 190 | ||
| 191 | // Only do immediate descendants | |
| 192 | if( subKey.indexOf( '.' ) == -1 ) { | |
| 193 | toRemove.add( propKey ); | |
| 194 | } | |
| 195 | } | |
| 196 | } | |
| 197 | ||
| 198 | // Remove them now that the enumeration is done with | |
| 199 | for( final String propKey : toRemove ) { | |
| 200 | p.remove( propKey ); | |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | // If this node hasn't been removed, add back in any values | |
| 205 | if( !mRemoved ) { | |
| 206 | for( final String s : mRoot.keySet() ) { | |
| 207 | p.setProperty( path + s, mRoot.get( s ) ); | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 211 | p.store( new FileOutputStream( file ), "FilePreferences" ); | |
| 212 | } catch( final Exception ex ) { | |
| 213 | error( new BackingStoreException( ex ) ); | |
| 214 | } | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | private void error( final BackingStoreException ex ) { | |
| 219 | getNotifier().notify( ex ); | |
| 220 | } | |
| 221 | ||
| 222 | private Notifier getNotifier() { | |
| 223 | return mNotifier; | |
| 224 | } | |
| 225 | } | |
| 1 | 226 |
| 1 | /* | |
| 2 | * Copyright 2016 David Croft and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.FileSystems; | |
| 32 | import java.util.prefs.Preferences; | |
| 33 | import java.util.prefs.PreferencesFactory; | |
| 34 | ||
| 35 | import static com.scrivenvar.Constants.APP_TITLE; | |
| 36 | ||
| 37 | /** | |
| 38 | * PreferencesFactory implementation that stores the preferences in a | |
| 39 | * user-defined file. Usage: | |
| 40 | * <pre> | |
| 41 | * System.setProperty( "java.util.prefs.PreferencesFactory", | |
| 42 | * FilePreferencesFactory.class.getName() ); | |
| 43 | * </pre> | |
| 44 | * <p> | |
| 45 | * The file defaults to <code>$user.home/.scrivenvar</code>, but can be changed | |
| 46 | * using <code>-Dapplication.name=preferences</code> when running the | |
| 47 | * application, or by calling <code>System.setProperty</code> with the | |
| 48 | * "application.name" property. | |
| 49 | * </p> | |
| 50 | */ | |
| 51 | public class FilePreferencesFactory implements PreferencesFactory { | |
| 52 | ||
| 53 | private static File preferencesFile; | |
| 54 | private Preferences rootPreferences; | |
| 55 | ||
| 56 | @Override | |
| 57 | public Preferences systemRoot() { | |
| 58 | return userRoot(); | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public synchronized Preferences userRoot() { | |
| 63 | if( rootPreferences == null ) { | |
| 64 | rootPreferences = new FilePreferences( null, "" ); | |
| 65 | } | |
| 66 | ||
| 67 | return rootPreferences; | |
| 68 | } | |
| 69 | ||
| 70 | public synchronized static File getPreferencesFile() { | |
| 71 | if( preferencesFile == null ) { | |
| 72 | String prefsFile = getPreferencesFilename(); | |
| 73 | ||
| 74 | preferencesFile = new File( prefsFile ).getAbsoluteFile(); | |
| 75 | } | |
| 76 | ||
| 77 | return preferencesFile; | |
| 78 | } | |
| 79 | ||
| 80 | public static String getPreferencesFilename() { | |
| 81 | final String filename = System.getProperty( "application.name", APP_TITLE ); | |
| 82 | return System.getProperty( "user.home" ) + getSeparator() + "." + filename; | |
| 83 | } | |
| 84 | ||
| 85 | public static String getSeparator() { | |
| 86 | return FileSystems.getDefault().getSeparator(); | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import com.dlsc.formsfx.model.structure.StringField; | |
| 31 | import com.dlsc.preferencesfx.PreferencesFx; | |
| 32 | import com.dlsc.preferencesfx.model.Category; | |
| 33 | import com.dlsc.preferencesfx.model.Group; | |
| 34 | import com.dlsc.preferencesfx.model.Setting; | |
| 35 | import com.scrivenvar.Services; | |
| 36 | import com.scrivenvar.service.Settings; | |
| 37 | import javafx.beans.property.ObjectProperty; | |
| 38 | import javafx.beans.property.SimpleObjectProperty; | |
| 39 | import javafx.beans.property.SimpleStringProperty; | |
| 40 | import javafx.beans.property.StringProperty; | |
| 41 | import javafx.scene.Node; | |
| 42 | import javafx.scene.control.Label; | |
| 43 | ||
| 44 | import java.io.File; | |
| 45 | import java.nio.file.Path; | |
| 46 | ||
| 47 | import static com.scrivenvar.Constants.PERSIST_IMAGES_DEFAULT; | |
| 48 | import static com.scrivenvar.Constants.USER_DIRECTORY; | |
| 49 | import static com.scrivenvar.Messages.get; | |
| 50 | ||
| 51 | public class UserPreferences { | |
| 52 | private final Settings SETTINGS = Services.load( Settings.class ); | |
| 53 | ||
| 54 | private final ObjectProperty<File> mPropRDirectory; | |
| 55 | private final StringProperty mPropRScript; | |
| 56 | private final ObjectProperty<File> mPropImagesDirectory; | |
| 57 | private final StringProperty mPropImagesOrder; | |
| 58 | private final ObjectProperty<File> mPropDefinitionPath; | |
| 59 | ||
| 60 | private final PreferencesFx mPreferencesFx; | |
| 61 | ||
| 62 | public UserPreferences() { | |
| 63 | mPropRDirectory = simpleFile( USER_DIRECTORY ); | |
| 64 | mPropRScript = new SimpleStringProperty( "" ); | |
| 65 | ||
| 66 | mPropImagesDirectory = simpleFile( USER_DIRECTORY ); | |
| 67 | mPropImagesOrder = new SimpleStringProperty( PERSIST_IMAGES_DEFAULT ); | |
| 68 | ||
| 69 | mPropDefinitionPath = simpleFile( getSetting( | |
| 70 | "file.definition.default", "variables.yaml" ) | |
| 71 | ); | |
| 72 | ||
| 73 | mPreferencesFx = createPreferencesFx(); | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Display the user preferences settings dialog (non-modal). | |
| 78 | */ | |
| 79 | public void show() { | |
| 80 | mPreferencesFx.show( false ); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Call to persist the settings. Strictly speaking, this could watch on | |
| 85 | * all values for external changes then save automatically. | |
| 86 | */ | |
| 87 | public void save() { | |
| 88 | mPreferencesFx.saveSettings(); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Creates the preferences dialog. | |
| 93 | * <p> | |
| 94 | * TODO: Make this dynamic by iterating over all "Preferences.*" values | |
| 95 | * that follow a particular naming pattern. | |
| 96 | * </p> | |
| 97 | * | |
| 98 | * @return A new instance of preferences for users to edit. | |
| 99 | */ | |
| 100 | @SuppressWarnings("unchecked") | |
| 101 | private PreferencesFx createPreferencesFx() { | |
| 102 | final Setting<StringField, StringProperty> scriptSetting = | |
| 103 | Setting.of( "Script", mPropRScript ); | |
| 104 | final StringField field = scriptSetting.getElement(); | |
| 105 | field.multiline( true ); | |
| 106 | ||
| 107 | return PreferencesFx.of( | |
| 108 | UserPreferences.class, | |
| 109 | Category.of( | |
| 110 | get( "Preferences.r" ), | |
| 111 | Group.of( | |
| 112 | get( "Preferences.r.directory" ), | |
| 113 | Setting.of( label( "Preferences.r.directory.desc", false ) ), | |
| 114 | Setting.of( "Directory", mPropRDirectory, true ) | |
| 115 | ), | |
| 116 | Group.of( | |
| 117 | get( "Preferences.r.script" ), | |
| 118 | Setting.of( label( "Preferences.r.script.desc" ) ), | |
| 119 | scriptSetting | |
| 120 | ) | |
| 121 | ), | |
| 122 | Category.of( | |
| 123 | get( "Preferences.images" ), | |
| 124 | Group.of( | |
| 125 | get( "Preferences.images.directory" ), | |
| 126 | Setting.of( label( "Preferences.images.directory.desc" ) ), | |
| 127 | Setting.of( "Directory", mPropImagesDirectory, true ) | |
| 128 | ), | |
| 129 | Group.of( | |
| 130 | get( "Preferences.images.suffixes" ), | |
| 131 | Setting.of( label( "Preferences.images.suffixes.desc" ) ), | |
| 132 | Setting.of( "Extensions", mPropImagesOrder ) | |
| 133 | ) | |
| 134 | ), | |
| 135 | Category.of( | |
| 136 | get( "Preferences.definitions" ), | |
| 137 | Group.of( | |
| 138 | get( "Preferences.definitions.path" ), | |
| 139 | Setting.of( label( "Preferences.definitions.path.desc" ) ), | |
| 140 | Setting.of( "Path", mPropDefinitionPath, false ) | |
| 141 | ) | |
| 142 | ) | |
| 143 | ); | |
| 144 | } | |
| 145 | ||
| 146 | /** | |
| 147 | * Wraps a {@link File} inside a {@link SimpleObjectProperty}. | |
| 148 | * | |
| 149 | * @param path The file name to use when constructing the {@link File}. | |
| 150 | * @return A new {@link SimpleObjectProperty} instance with a {@link File} | |
| 151 | * that references the given {@code path}. | |
| 152 | */ | |
| 153 | private SimpleObjectProperty<File> simpleFile( final String path ) { | |
| 154 | return new SimpleObjectProperty<>( new File( path ) ); | |
| 155 | } | |
| 156 | ||
| 157 | /** | |
| 158 | * Creates a label for the given key after interpolating its value. | |
| 159 | * | |
| 160 | * @param key The key to find in the resource bundle. | |
| 161 | * @return The value of the key as a label. | |
| 162 | */ | |
| 163 | private Node label( final String key ) { | |
| 164 | return new Label( get( key, true ) ); | |
| 165 | } | |
| 166 | ||
| 167 | /** | |
| 168 | * Creates a label for the given key. | |
| 169 | * | |
| 170 | * @param key The key to find in the resource bundle. | |
| 171 | * @param interpolate {@code true} means to interpolate the value. | |
| 172 | * @return The value of the key, interpolated if {@code interpolate} is | |
| 173 | * {@code true}. | |
| 174 | */ | |
| 175 | @SuppressWarnings("SameParameterValue") | |
| 176 | private Node label( final String key, final boolean interpolate ) { | |
| 177 | return new Label( get( key, interpolate ) ); | |
| 178 | } | |
| 179 | ||
| 180 | /** | |
| 181 | * Returns the value for a key from the settings properties file. | |
| 182 | * | |
| 183 | * @param key Key within the settings properties file to find. | |
| 184 | * @param value Default value to return if the key is not found. | |
| 185 | * @return The value for the given key from the settings file, or the | |
| 186 | * given {@code value} if no key found. | |
| 187 | */ | |
| 188 | @SuppressWarnings("SameParameterValue") | |
| 189 | private String getSetting( final String key, final String value ) { | |
| 190 | return SETTINGS.getSetting( key, value ); | |
| 191 | } | |
| 192 | ||
| 193 | public ObjectProperty<File> definitionPathProperty() { | |
| 194 | return mPropDefinitionPath; | |
| 195 | } | |
| 196 | ||
| 197 | public Path getDefinitionPath() { | |
| 198 | return definitionPathProperty().getValue().toPath(); | |
| 199 | } | |
| 200 | ||
| 201 | private ObjectProperty<File> rDirectoryProperty() { | |
| 202 | return mPropRDirectory; | |
| 203 | } | |
| 204 | ||
| 205 | public File getRDirectory() { | |
| 206 | return rDirectoryProperty().getValue(); | |
| 207 | } | |
| 208 | ||
| 209 | private StringProperty rScriptProperty() { | |
| 210 | return mPropRScript; | |
| 211 | } | |
| 212 | ||
| 213 | public String getRScript() { | |
| 214 | return rScriptProperty().getValue(); | |
| 215 | } | |
| 216 | ||
| 217 | private ObjectProperty<File> imagesDirectoryProperty() { | |
| 218 | return mPropImagesDirectory; | |
| 219 | } | |
| 220 | ||
| 221 | public File getImagesDirectory() { | |
| 222 | return imagesDirectoryProperty().getValue(); | |
| 223 | } | |
| 224 | ||
| 225 | private StringProperty imagesOrderProperty() { | |
| 226 | return mPropImagesOrder; | |
| 227 | } | |
| 228 | ||
| 229 | public String getImagesOrder() { | |
| 230 | return imagesOrderProperty().getValue(); | |
| 231 | } | |
| 232 | } | |
| 1 | 233 |
| 1 | /* | |
| 2 | * {{{ header & license | |
| 3 | * Copyright (c) 2006 Patrick Wright | |
| 4 | * Copyright (c) 2007 Wisconsin Court System | |
| 5 | * | |
| 6 | * This program is free software; you can redistribute it and/or | |
| 7 | * modify it under the terms of the GNU Lesser General Public License | |
| 8 | * as published by the Free Software Foundation; either version 2.1 | |
| 9 | * of the License, or (at your option) any later version. | |
| 10 | * | |
| 11 | * This program is distributed in the hope that it will be useful, | |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 14 | * GNU Lesser General Public License for more details. | |
| 15 | * | |
| 16 | * You should have received a copy of the GNU Lesser General Public License | |
| 17 | * along with this program; if not, write to the Free Software | |
| 18 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| 19 | * }}} | |
| 20 | */ | |
| 21 | package com.scrivenvar.preview; | |
| 22 | ||
| 23 | import org.w3c.dom.Element; | |
| 24 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 25 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 26 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 27 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 28 | import org.xhtmlrenderer.render.BlockBox; | |
| 29 | import org.xhtmlrenderer.simple.extend.FormSubmissionListener; | |
| 30 | ||
| 31 | import java.util.ArrayList; | |
| 32 | import java.util.List; | |
| 33 | ||
| 34 | public class ChainedReplacedElementFactory implements ReplacedElementFactory { | |
| 35 | private final List<ReplacedElementFactory> mFactoryList = new ArrayList<>(); | |
| 36 | ||
| 37 | public ChainedReplacedElementFactory() { | |
| 38 | } | |
| 39 | ||
| 40 | public ReplacedElement createReplacedElement( | |
| 41 | LayoutContext c, BlockBox box, UserAgentCallback uac, | |
| 42 | int cssWidth, int cssHeight ) { | |
| 43 | ReplacedElement re = null; | |
| 44 | ||
| 45 | for( final ReplacedElementFactory ref : mFactoryList ) { | |
| 46 | re = ref.createReplacedElement( c, box, uac, cssWidth, cssHeight ); | |
| 47 | ||
| 48 | if( re != null ) { | |
| 49 | break; | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | return re; | |
| 54 | } | |
| 55 | ||
| 56 | public void addFactory( final ReplacedElementFactory factory ) { | |
| 57 | mFactoryList.add( factory ); | |
| 58 | } | |
| 59 | ||
| 60 | public void reset() { | |
| 61 | for( final ReplacedElementFactory factory : mFactoryList ) { | |
| 62 | factory.reset(); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | public void remove( final Element element ) { | |
| 67 | for( final ReplacedElementFactory factory : mFactoryList ) { | |
| 68 | factory.remove( element ); | |
| 69 | } | |
| 70 | } | |
| 71 | ||
| 72 | public void setFormSubmissionListener( FormSubmissionListener listener ) { | |
| 73 | } | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import javafx.embed.swing.SwingNode; | |
| 31 | import javafx.scene.Node; | |
| 32 | import javafx.scene.layout.Pane; | |
| 33 | import org.jsoup.Jsoup; | |
| 34 | import org.jsoup.helper.W3CDom; | |
| 35 | import org.jsoup.nodes.Document; | |
| 36 | import org.xhtmlrenderer.simple.XHTMLPanel; | |
| 37 | import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; | |
| 38 | import org.xhtmlrenderer.swing.SwingReplacedElementFactory; | |
| 39 | ||
| 40 | import javax.swing.*; | |
| 41 | import java.nio.file.Path; | |
| 42 | ||
| 43 | import static com.scrivenvar.Constants.STYLESHEET_PREVIEW; | |
| 44 | ||
| 45 | /** | |
| 46 | * HTML preview pane is responsible for rendering an HTML document. | |
| 47 | * | |
| 48 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 49 | */ | |
| 50 | public final class HTMLPreviewPane extends Pane { | |
| 51 | private static class HTMLPanel extends XHTMLPanel { | |
| 52 | /** | |
| 53 | * Prevent scrolling to the top. | |
| 54 | */ | |
| 55 | @Override | |
| 56 | public void resetScrollPosition() { | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | private final static String HTML_HEADER = "<!DOCTYPE html>" | |
| 61 | + "<html>" | |
| 62 | + "<head>" | |
| 63 | + "<link rel='stylesheet' href='" + | |
| 64 | HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>" | |
| 65 | + "</head>" | |
| 66 | + "<body>"; | |
| 67 | private final static String HTML_FOOTER = "</body></html>"; | |
| 68 | ||
| 69 | private final StringBuilder mHtml = new StringBuilder( 65536 ); | |
| 70 | private final int mHtmlPrefixLength; | |
| 71 | ||
| 72 | private final W3CDom mW3cDom = new W3CDom(); | |
| 73 | private final XhtmlNamespaceHandler mNamespaceHandler = | |
| 74 | new XhtmlNamespaceHandler(); | |
| 75 | private final HTMLPanel mRenderer = new HTMLPanel(); | |
| 76 | private final SwingNode mSwingNode = new SwingNode(); | |
| 77 | private final JScrollPane mScrollPane = new JScrollPane( mRenderer ); | |
| 78 | ||
| 79 | private Path mPath; | |
| 80 | ||
| 81 | /** | |
| 82 | * Creates a new preview pane that can scroll to the caret position within the | |
| 83 | * document. | |
| 84 | */ | |
| 85 | public HTMLPreviewPane() { | |
| 86 | final ChainedReplacedElementFactory factory = | |
| 87 | new ChainedReplacedElementFactory(); | |
| 88 | factory.addFactory( new SVGReplacedElementFactory() ); | |
| 89 | factory.addFactory( new SwingReplacedElementFactory() ); | |
| 90 | ||
| 91 | mRenderer.getSharedContext().setReplacedElementFactory( factory ); | |
| 92 | mRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold( 0 ); | |
| 93 | mSwingNode.setContent( mScrollPane ); | |
| 94 | ||
| 95 | mHtml.append( HTML_HEADER ); | |
| 96 | mHtmlPrefixLength = mHtml.length(); | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * Updates the internal HTML source, loads it into the preview pane, then | |
| 101 | * scrolls to the caret position. | |
| 102 | * | |
| 103 | * @param html The new HTML document to display. | |
| 104 | */ | |
| 105 | public void update( final String html ) { | |
| 106 | final Document jsoupDoc = Jsoup.parse( decorate( html ) ); | |
| 107 | org.w3c.dom.Document w3cDoc = mW3cDom.fromJsoup( jsoupDoc ); | |
| 108 | ||
| 109 | mRenderer.setDocument( w3cDoc, getBaseUrl(), mNamespaceHandler ); | |
| 110 | } | |
| 111 | ||
| 112 | private String decorate( final String html ) { | |
| 113 | mHtml.setLength( mHtmlPrefixLength ); | |
| 114 | return mHtml.append( html ) | |
| 115 | .append( HTML_FOOTER ) | |
| 116 | .toString(); | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Clears out the HTML content from the preview. | |
| 121 | */ | |
| 122 | public void clear() { | |
| 123 | update( "" ); | |
| 124 | } | |
| 125 | ||
| 126 | private String getBaseUrl() { | |
| 127 | final Path basePath = getPath(); | |
| 128 | final Path parent = basePath == null ? null : basePath.getParent(); | |
| 129 | ||
| 130 | return parent == null ? "" : parent.toUri().toString(); | |
| 131 | } | |
| 132 | ||
| 133 | public Path getPath() { | |
| 134 | return mPath; | |
| 135 | } | |
| 136 | ||
| 137 | public void setPath( final Path path ) { | |
| 138 | assert path != null; | |
| 139 | mPath = path; | |
| 140 | } | |
| 141 | ||
| 142 | /** | |
| 143 | * Content to embed in a panel. | |
| 144 | * | |
| 145 | * @return The content to display to the user. | |
| 146 | */ | |
| 147 | public Node getNode() { | |
| 148 | return mSwingNode; | |
| 149 | } | |
| 150 | ||
| 151 | public JScrollPane getScrollPane() { | |
| 152 | return mScrollPane; | |
| 153 | } | |
| 154 | ||
| 155 | public JScrollBar getVerticalScrollBar() { | |
| 156 | return getScrollPane().getVerticalScrollBar(); | |
| 157 | } | |
| 158 | } | |
| 1 | 159 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import org.apache.batik.anim.dom.SAXSVGDocumentFactory; | |
| 31 | import org.apache.batik.gvt.renderer.ImageRenderer; | |
| 32 | import org.apache.batik.transcoder.TranscoderException; | |
| 33 | import org.apache.batik.transcoder.TranscoderInput; | |
| 34 | import org.apache.batik.transcoder.TranscoderOutput; | |
| 35 | import org.apache.batik.transcoder.image.ImageTranscoder; | |
| 36 | import org.w3c.dom.svg.SVGDocument; | |
| 37 | ||
| 38 | import java.awt.*; | |
| 39 | import java.awt.image.BufferedImage; | |
| 40 | import java.io.IOException; | |
| 41 | import java.net.URL; | |
| 42 | import java.util.Map; | |
| 43 | ||
| 44 | import static java.awt.Color.WHITE; | |
| 45 | import static java.awt.RenderingHints.*; | |
| 46 | import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH; | |
| 47 | import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_BACKGROUND_COLOR; | |
| 48 | import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName; | |
| 49 | ||
| 50 | public class SVGRasterizer { | |
| 51 | private final static SAXSVGDocumentFactory mFactory = | |
| 52 | new SAXSVGDocumentFactory( getXMLParserClassName() ); | |
| 53 | ||
| 54 | private final static Map<Object, Object> RENDERING_HINTS = Map.of( | |
| 55 | KEY_ALPHA_INTERPOLATION, | |
| 56 | VALUE_ALPHA_INTERPOLATION_QUALITY, | |
| 57 | KEY_INTERPOLATION, | |
| 58 | VALUE_INTERPOLATION_BICUBIC, | |
| 59 | KEY_ANTIALIASING, | |
| 60 | VALUE_ANTIALIAS_ON, | |
| 61 | KEY_COLOR_RENDERING, | |
| 62 | VALUE_COLOR_RENDER_QUALITY, | |
| 63 | KEY_DITHERING, | |
| 64 | VALUE_DITHER_DISABLE, | |
| 65 | KEY_RENDERING, | |
| 66 | VALUE_RENDER_QUALITY, | |
| 67 | KEY_STROKE_CONTROL, | |
| 68 | VALUE_STROKE_PURE, | |
| 69 | KEY_FRACTIONALMETRICS, | |
| 70 | VALUE_FRACTIONALMETRICS_ON, | |
| 71 | KEY_TEXT_ANTIALIASING, | |
| 72 | VALUE_TEXT_ANTIALIAS_OFF | |
| 73 | ); | |
| 74 | ||
| 75 | private static class BufferedImageTranscoder extends ImageTranscoder { | |
| 76 | private BufferedImage mImage; | |
| 77 | ||
| 78 | @Override | |
| 79 | public BufferedImage createImage( final int w, final int h ) { | |
| 80 | return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB ); | |
| 81 | } | |
| 82 | ||
| 83 | @Override | |
| 84 | public void writeImage( | |
| 85 | final BufferedImage image, final TranscoderOutput output ) { | |
| 86 | mImage = image; | |
| 87 | } | |
| 88 | ||
| 89 | public BufferedImage getBufferedImage() { | |
| 90 | return mImage; | |
| 91 | } | |
| 92 | ||
| 93 | @Override | |
| 94 | protected ImageRenderer createRenderer() { | |
| 95 | final ImageRenderer renderer = super.createRenderer(); | |
| 96 | final RenderingHints hints = renderer.getRenderingHints(); | |
| 97 | hints.putAll( RENDERING_HINTS ); | |
| 98 | ||
| 99 | renderer.setRenderingHints( hints ); | |
| 100 | ||
| 101 | return renderer; | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | public static BufferedImage rasterize( final String url, final int width ) | |
| 106 | throws IOException, TranscoderException { | |
| 107 | return rasterize( new URL( url ), width ); | |
| 108 | } | |
| 109 | ||
| 110 | public static BufferedImage rasterize( final URL url, final int width ) | |
| 111 | throws IOException, TranscoderException { | |
| 112 | return rasterize( | |
| 113 | (SVGDocument) mFactory.createDocument( url.toString() ), width ); | |
| 114 | } | |
| 115 | ||
| 116 | public static BufferedImage rasterize( | |
| 117 | final SVGDocument svg, final int width ) throws TranscoderException { | |
| 118 | final var transcoder = new BufferedImageTranscoder(); | |
| 119 | final var input = new TranscoderInput( svg ); | |
| 120 | ||
| 121 | transcoder.addTranscodingHint( KEY_BACKGROUND_COLOR, WHITE ); | |
| 122 | transcoder.addTranscodingHint( KEY_WIDTH, (float) width ); | |
| 123 | transcoder.transcode( input, null ); | |
| 124 | ||
| 125 | return transcoder.getBufferedImage(); | |
| 126 | } | |
| 127 | } | |
| 1 | 128 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | import org.apache.commons.io.FilenameUtils; | |
| 33 | import org.w3c.dom.Element; | |
| 34 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 35 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 36 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 37 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 38 | import org.xhtmlrenderer.render.BlockBox; | |
| 39 | import org.xhtmlrenderer.simple.extend.FormSubmissionListener; | |
| 40 | import org.xhtmlrenderer.swing.ImageReplacedElement; | |
| 41 | ||
| 42 | import java.awt.*; | |
| 43 | ||
| 44 | public class SVGReplacedElementFactory | |
| 45 | implements ReplacedElementFactory { | |
| 46 | ||
| 47 | private final static Notifier sNotifier = Services.load( Notifier.class ); | |
| 48 | ||
| 49 | /** | |
| 50 | * SVG filename extension. | |
| 51 | */ | |
| 52 | private static final String SVG_FILE = "svg"; | |
| 53 | private static final String HTML_IMAGE = "img"; | |
| 54 | private static final String HTML_IMAGE_SRC = "src"; | |
| 55 | ||
| 56 | public ReplacedElement createReplacedElement( | |
| 57 | final LayoutContext c, final BlockBox box, final UserAgentCallback uac, | |
| 58 | final int cssWidth, final int cssHeight ) { | |
| 59 | final Element e = box.getElement(); | |
| 60 | ||
| 61 | if( e == null ) { | |
| 62 | return null; | |
| 63 | } | |
| 64 | ||
| 65 | final String nodeName = e.getNodeName(); | |
| 66 | ReplacedElement result = null; | |
| 67 | ||
| 68 | if( HTML_IMAGE.equals( nodeName ) ) { | |
| 69 | final String src = e.getAttribute( HTML_IMAGE_SRC ); | |
| 70 | final String ext = FilenameUtils.getExtension( src ); | |
| 71 | ||
| 72 | if( SVG_FILE.equalsIgnoreCase( ext ) ) { | |
| 73 | try { | |
| 74 | final int width = box.getContentWidth(); | |
| 75 | final Image image = SVGRasterizer.rasterize( src, width ); | |
| 76 | ||
| 77 | final int w = image.getWidth( null ); | |
| 78 | final int h = image.getHeight( null ); | |
| 79 | ||
| 80 | result = new ImageReplacedElement( image, w, h ); | |
| 81 | } catch( final Exception ex ) { | |
| 82 | getNotifier().notify( ex ); | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | return result; | |
| 88 | } | |
| 89 | ||
| 90 | @Override | |
| 91 | public void reset() { | |
| 92 | } | |
| 93 | ||
| 94 | @Override | |
| 95 | public void remove( Element e ) { | |
| 96 | } | |
| 97 | ||
| 98 | @Override | |
| 99 | public void setFormSubmissionListener( FormSubmissionListener listener ) { | |
| 100 | } | |
| 101 | ||
| 102 | private Notifier getNotifier() { | |
| 103 | return sNotifier; | |
| 104 | } | |
| 105 | } | |
| 1 | 106 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for transforming a document through a variety of chained | |
| 32 | * handlers. If there are conditions where this handler should not process the | |
| 33 | * entire chain, create a second handler, or split the chain into reusable | |
| 34 | * sub-chains. | |
| 35 | * | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | * @param <T> The type of object to process. | |
| 38 | */ | |
| 39 | public abstract class AbstractProcessor<T> implements Processor<T> { | |
| 40 | ||
| 41 | /** | |
| 42 | * Used while processing the entire chain; null to signify no more links. | |
| 43 | */ | |
| 44 | private final Processor<T> mNext; | |
| 45 | ||
| 46 | /** | |
| 47 | * Constructs a succession without a successor (i.e., next is null). | |
| 48 | */ | |
| 49 | protected AbstractProcessor() { | |
| 50 | this( null ); | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Constructs a new default handler with a given successor. | |
| 55 | * | |
| 56 | * @param successor Use null to indicate last link in the chain. | |
| 57 | */ | |
| 58 | public AbstractProcessor( final Processor<T> successor ) { | |
| 59 | mNext = successor; | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Processes links in the chain while there are successors and valid data to | |
| 64 | * process. | |
| 65 | * | |
| 66 | * @param t The object to process. | |
| 67 | */ | |
| 68 | @Override | |
| 69 | public synchronized void processChain( T t ) { | |
| 70 | Processor<T> handler = this; | |
| 71 | ||
| 72 | while( handler != null && t != null ) { | |
| 73 | t = handler.processLink( t ); | |
| 74 | handler = handler.next(); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | @Override | |
| 79 | public Processor<T> next() { | |
| 80 | return mNext; | |
| 81 | } | |
| 82 | } | |
| 1 | 83 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | import static com.scrivenvar.processors.text.TextReplacementFactory.replace; | |
| 33 | ||
| 34 | /** | |
| 35 | * Processes interpolated string definitions in the document and inserts | |
| 36 | * their values into the post-processed text. The default variable syntax is | |
| 37 | * {@code $variable$}. | |
| 38 | * | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class DefinitionProcessor extends AbstractProcessor<String> { | |
| 42 | ||
| 43 | private final Map<String, String> mDefinitions; | |
| 44 | ||
| 45 | public DefinitionProcessor( | |
| 46 | final Processor<String> successor, final Map<String, String> map ) { | |
| 47 | super( successor ); | |
| 48 | mDefinitions = map; | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * Processes the given text document by replacing variables with their values. | |
| 53 | * | |
| 54 | * @param text The document text that includes variables that should be | |
| 55 | * replaced with values when rendered as HTML. | |
| 56 | * @return The text with all variables replaced. | |
| 57 | */ | |
| 58 | @Override | |
| 59 | public String processLink( final String text ) { | |
| 60 | return replace( text, getDefinitions() ); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Returns the map to use for variable substitution. | |
| 65 | * | |
| 66 | * @return A map of variable names to values. | |
| 67 | */ | |
| 68 | protected Map<String, String> getDefinitions() { | |
| 69 | return mDefinitions; | |
| 70 | } | |
| 71 | } | |
| 1 | 72 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for notifying the HTMLPreviewPane when the succession chain has | |
| 34 | * updated. This decouples knowledge of changes to the editor panel from the | |
| 35 | * HTML preview panel as well as any processing that takes place before the | |
| 36 | * final HTML preview is rendered. This should be the last link in the processor | |
| 37 | * chain. | |
| 38 | * | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class HTMLPreviewProcessor extends AbstractProcessor<String> { | |
| 42 | ||
| 43 | // There is only one preview panel. | |
| 44 | private static HTMLPreviewPane sHtmlPreviewPane; | |
| 45 | ||
| 46 | /** | |
| 47 | * Constructs the end of a processing chain. | |
| 48 | * | |
| 49 | * @param htmlPreviewPane The pane to update with the post-processed document. | |
| 50 | */ | |
| 51 | public HTMLPreviewProcessor( final HTMLPreviewPane htmlPreviewPane ) { | |
| 52 | sHtmlPreviewPane = htmlPreviewPane; | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * Update the preview panel using HTML from the succession chain. | |
| 57 | * | |
| 58 | * @param html The document content to render in the preview pane. The HTML | |
| 59 | * should not contain a doctype, head, or body tag, only content to render | |
| 60 | * within the body. | |
| 61 | * | |
| 62 | * @return null | |
| 63 | */ | |
| 64 | @Override | |
| 65 | public String processLink( final String html ) { | |
| 66 | getHtmlPreviewPane().update( html ); | |
| 67 | ||
| 68 | // No more processing required. | |
| 69 | return null; | |
| 70 | } | |
| 71 | ||
| 72 | private HTMLPreviewPane getHtmlPreviewPane() { | |
| 73 | return sHtmlPreviewPane; | |
| 74 | } | |
| 75 | } | |
| 1 | 76 |
| 1 | /* | |
| 2 | * Copyright 2017 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | /** | |
| 31 | * This is the default processor used when an unknown filename extension is | |
| 32 | * encountered. | |
| 33 | * | |
| 34 | * @author White Magic Software, Ltd. | |
| 35 | */ | |
| 36 | public class IdentityProcessor extends AbstractProcessor<String> { | |
| 37 | ||
| 38 | /** | |
| 39 | * Passes the link to the super constructor. | |
| 40 | * | |
| 41 | * @param link The next processor in the chain to use for text processing. | |
| 42 | */ | |
| 43 | public IdentityProcessor( final Processor<String> link ) { | |
| 44 | super( link ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the given string, modified with "pre" tags. | |
| 49 | * | |
| 50 | * @param t The string to return, enclosed in "pre" tags. | |
| 51 | * @return The value of t wrapped in "pre" tags. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public String processLink( final String t ) { | |
| 55 | return "<pre>" + t + "</pre>"; | |
| 56 | } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.preferences.UserPreferences; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import com.scrivenvar.service.events.Notifier; | |
| 34 | ||
| 35 | import javax.script.ScriptEngine; | |
| 36 | import javax.script.ScriptEngineManager; | |
| 37 | import javax.script.ScriptException; | |
| 38 | import java.nio.file.Path; | |
| 39 | import java.util.LinkedHashMap; | |
| 40 | import java.util.Map; | |
| 41 | ||
| 42 | import static com.scrivenvar.Constants.*; | |
| 43 | import static com.scrivenvar.Messages.get; | |
| 44 | import static com.scrivenvar.decorators.RVariableDecorator.PREFIX; | |
| 45 | import static com.scrivenvar.decorators.RVariableDecorator.SUFFIX; | |
| 46 | import static com.scrivenvar.processors.text.TextReplacementFactory.replace; | |
| 47 | import static java.lang.Math.min; | |
| 48 | ||
| 49 | /** | |
| 50 | * Transforms a document containing R statements into Markdown. | |
| 51 | * | |
| 52 | * @author White Magic Software, Ltd. | |
| 53 | */ | |
| 54 | public final class InlineRProcessor extends DefinitionProcessor { | |
| 55 | ||
| 56 | private static final Notifier NOTIFIER = Services.load( Notifier.class ); | |
| 57 | private static final Options OPTIONS = Services.load( Options.class ); | |
| 58 | ||
| 59 | /** | |
| 60 | * Constrain memory when typing new R expressions into the document. | |
| 61 | */ | |
| 62 | private static final int MAX_CACHED_R_STATEMENTS = 512; | |
| 63 | ||
| 64 | /** | |
| 65 | * Only one editor is open at a time. | |
| 66 | */ | |
| 67 | private static final ScriptEngine ENGINE = | |
| 68 | (new ScriptEngineManager()).getEngineByName( "Renjin" ); | |
| 69 | ||
| 70 | /** | |
| 71 | * Where to put document inline evaluated R expressions. | |
| 72 | */ | |
| 73 | private final Map<String, Object> mEvalCache = new LinkedHashMap<>() { | |
| 74 | @Override | |
| 75 | protected boolean removeEldestEntry( | |
| 76 | final Map.Entry<String, Object> eldest ) { | |
| 77 | return size() > MAX_CACHED_R_STATEMENTS; | |
| 78 | } | |
| 79 | }; | |
| 80 | ||
| 81 | /** | |
| 82 | * Constructs a processor capable of evaluating R statements. | |
| 83 | * | |
| 84 | * @param processor Subsequent link in the processing chain. | |
| 85 | * @param map Resolved definitions map. | |
| 86 | */ | |
| 87 | public InlineRProcessor( | |
| 88 | final Processor<String> processor, | |
| 89 | final Map<String, String> map ) { | |
| 90 | super( processor, map ); | |
| 91 | init(); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Initialises the R code so that R can find imported libraries. | |
| 96 | */ | |
| 97 | private void init() { | |
| 98 | try { | |
| 99 | final Path wd = getWorkingDirectory(); | |
| 100 | final String dir = wd.toString().replace( '\\', '/' ); | |
| 101 | final Map<String, String> map = getDefinitions(); | |
| 102 | map.put( "$application.r.working.directory$", dir ); | |
| 103 | ||
| 104 | final String bootstrap = getBootstrapScript(); | |
| 105 | ||
| 106 | if( !bootstrap.isBlank() ) { | |
| 107 | eval( replace( bootstrap, map ) ); | |
| 108 | } | |
| 109 | } catch( final Exception e ) { | |
| 110 | getNotifier().notify( e ); | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Evaluates all R statements in the source document and inserts the | |
| 116 | * calculated value into the generated document. | |
| 117 | * | |
| 118 | * @param text The document text that includes variables that should be | |
| 119 | * replaced with values when rendered as HTML. | |
| 120 | * @return The generated document with output from all R statements | |
| 121 | * substituted with value returned from their execution. | |
| 122 | */ | |
| 123 | @Override | |
| 124 | public String processLink( final String text ) { | |
| 125 | final int length = text.length(); | |
| 126 | final int prefixLength = PREFIX.length(); | |
| 127 | ||
| 128 | // The * 2 is a wild guess at the ratio of R statements to the length | |
| 129 | // of text produced by those statements. | |
| 130 | final StringBuilder sb = new StringBuilder( length * 2 ); | |
| 131 | ||
| 132 | int prevIndex = 0; | |
| 133 | int currIndex = text.indexOf( PREFIX ); | |
| 134 | ||
| 135 | while( currIndex >= 0 ) { | |
| 136 | // Copy everything up to, but not including, an R statement (`r#). | |
| 137 | sb.append( text, prevIndex, currIndex ); | |
| 138 | ||
| 139 | // Jump to the start of the R statement. | |
| 140 | prevIndex = currIndex + prefixLength; | |
| 141 | ||
| 142 | // Find the statement ending (`), without indexing past the text boundary. | |
| 143 | currIndex = text.indexOf( SUFFIX, min( currIndex + 1, length ) ); | |
| 144 | ||
| 145 | // Only evaluate inline R statements that have end delimiters. | |
| 146 | if( currIndex > 1 ) { | |
| 147 | // Extract the inline R statement to be evaluated. | |
| 148 | final String r = text.substring( prevIndex, currIndex ); | |
| 149 | ||
| 150 | // Pass the R statement into the R engine for evaluation. | |
| 151 | try { | |
| 152 | final Object result = evalText( r ); | |
| 153 | ||
| 154 | // Append the string representation of the result into the text. | |
| 155 | sb.append( result ); | |
| 156 | } catch( final Exception e ) { | |
| 157 | // If the string couldn't be parsed using R, append the statement | |
| 158 | // that failed to parse, instead of its evaluated value. | |
| 159 | sb.append( PREFIX ).append( r ).append( SUFFIX ); | |
| 160 | ||
| 161 | // Tell the user that there was a problem. | |
| 162 | getNotifier().notify( | |
| 163 | get( STATUS_PARSE_ERROR, e.getMessage(), currIndex ) | |
| 164 | ); | |
| 165 | } | |
| 166 | ||
| 167 | // Retain the R statement's ending position in the text. | |
| 168 | prevIndex = currIndex + 1; | |
| 169 | } | |
| 170 | ||
| 171 | // Find the start of the next inline R statement. | |
| 172 | currIndex = text.indexOf( PREFIX, min( currIndex + 1, length ) ); | |
| 173 | } | |
| 174 | ||
| 175 | // Copy from the previous index to the end of the string. | |
| 176 | return sb.append( text.substring( min( prevIndex, length ) ) ).toString(); | |
| 177 | } | |
| 178 | ||
| 179 | /** | |
| 180 | * Look up an R expression from the cache then return the resulting object. | |
| 181 | * If the R expression hasn't been cached, it'll first be evalulated. | |
| 182 | * | |
| 183 | * @param r The expression to evaluate. | |
| 184 | * @return The object resulting from the evaluation. | |
| 185 | */ | |
| 186 | private Object evalText( final String r ) { | |
| 187 | return mEvalCache.computeIfAbsent( r, v -> eval( r ) ); | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Evaluate an R expression and return the resulting object. | |
| 192 | * | |
| 193 | * @param r The expression to evaluate. | |
| 194 | * @return The object resulting from the evaluation. | |
| 195 | */ | |
| 196 | private Object eval( final String r ) { | |
| 197 | try { | |
| 198 | return getScriptEngine().eval( r ); | |
| 199 | } catch( final ScriptException e ) { | |
| 200 | getNotifier().notify( e ); | |
| 201 | return ""; | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | /** | |
| 206 | * This will return the given path if not null, otherwise it will return | |
| 207 | * the path to the user's directory. | |
| 208 | * | |
| 209 | * @return A non-null path. | |
| 210 | */ | |
| 211 | private Path getWorkingDirectory() { | |
| 212 | return getUserPreferences().getRDirectory().toPath(); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Loads the R init script from the application's persisted preferences. | |
| 217 | * | |
| 218 | * @return A non-null String, possibly empty. | |
| 219 | */ | |
| 220 | private String getBootstrapScript() { | |
| 221 | return getUserPreferences().getRScript(); | |
| 222 | } | |
| 223 | ||
| 224 | private UserPreferences getUserPreferences() { | |
| 225 | return getOptions().getUserPreferences(); | |
| 226 | } | |
| 227 | ||
| 228 | private ScriptEngine getScriptEngine() { | |
| 229 | return ENGINE; | |
| 230 | } | |
| 231 | ||
| 232 | private Notifier getNotifier() { | |
| 233 | return NOTIFIER; | |
| 234 | } | |
| 235 | ||
| 236 | private Options getOptions() { | |
| 237 | return OPTIONS; | |
| 238 | } | |
| 239 | } | |
| 1 | 240 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for processing documents from one known format to another. | |
| 32 | * | |
| 33 | * @param <T> The type of processor to create. | |
| 34 | * @author White Magic Software, Ltd. | |
| 35 | */ | |
| 36 | public interface Processor<T> { | |
| 37 | ||
| 38 | /** | |
| 39 | * Provided so that the chain can be invoked from any link using a given | |
| 40 | * value. This should be called automatically by a superclass so that | |
| 41 | * the links in the chain need only implement the processLink method. | |
| 42 | * | |
| 43 | * @param t The value to pass along to each link in the chain. | |
| 44 | */ | |
| 45 | void processChain( T t ); | |
| 46 | ||
| 47 | /** | |
| 48 | * Processes the given content providing a transformation from one document | |
| 49 | * format into another. For example, this could convert from XML to text using | |
| 50 | * an XSLT processor, or from markdown to HTML. | |
| 51 | * | |
| 52 | * @param t The type of object to process. | |
| 53 | * @return The post-processed document, or null if processing should stop. | |
| 54 | */ | |
| 55 | T processLink( T t ); | |
| 56 | ||
| 57 | /** | |
| 58 | * Adds a document processor to call after this processor finishes processing | |
| 59 | * the document given to the process method. | |
| 60 | * | |
| 61 | * @return The processor that should transform the document after this | |
| 62 | * instance has finished processing. | |
| 63 | */ | |
| 64 | Processor<T> next(); | |
| 65 | } | |
| 1 | 66 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.FileEditorTab; | |
| 32 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 33 | import com.scrivenvar.processors.markdown.MarkdownProcessor; | |
| 34 | ||
| 35 | import java.nio.file.Path; | |
| 36 | import java.util.Map; | |
| 37 | ||
| 38 | /** | |
| 39 | * Responsible for creating processors capable of parsing, transforming, | |
| 40 | * interpolating, and rendering known file types. | |
| 41 | * | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class ProcessorFactory extends AbstractFileFactory { | |
| 45 | ||
| 46 | private final HTMLPreviewPane mPreviewPane; | |
| 47 | private final Map<String, String> mResolvedMap; | |
| 48 | private final Processor<String> mMarkdownProcessor; | |
| 49 | ||
| 50 | /** | |
| 51 | * Constructs a factory with the ability to create processors that can perform | |
| 52 | * text and caret processing to generate a final preview. | |
| 53 | * | |
| 54 | * @param previewPane Where the final output is rendered. | |
| 55 | * @param resolvedMap Flat map of definitions to replace before final render. | |
| 56 | */ | |
| 57 | public ProcessorFactory( | |
| 58 | final HTMLPreviewPane previewPane, | |
| 59 | final Map<String, String> resolvedMap ) { | |
| 60 | mPreviewPane = previewPane; | |
| 61 | mResolvedMap = resolvedMap; | |
| 62 | mMarkdownProcessor = createMarkdownProcessor(); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Creates a processor suitable for parsing and rendering the file opened at | |
| 67 | * the given tab. | |
| 68 | * | |
| 69 | * @param tab The tab containing a text editor, path, and caret position. | |
| 70 | * @return A processor that can render the given tab's text. | |
| 71 | */ | |
| 72 | public Processor<String> createProcessor( final FileEditorTab tab ) { | |
| 73 | final Path path = tab.getPath(); | |
| 74 | final Processor<String> processor; | |
| 75 | ||
| 76 | switch( lookup( path ) ) { | |
| 77 | case RMARKDOWN: | |
| 78 | processor = createRProcessor(); | |
| 79 | break; | |
| 80 | ||
| 81 | case SOURCE: | |
| 82 | processor = createMarkdownDefinitionProcessor(); | |
| 83 | break; | |
| 84 | ||
| 85 | case XML: | |
| 86 | processor = createXMLProcessor( tab ); | |
| 87 | break; | |
| 88 | ||
| 89 | case RXML: | |
| 90 | processor = createRXMLProcessor( tab ); | |
| 91 | break; | |
| 92 | ||
| 93 | default: | |
| 94 | processor = createIdentityProcessor(); | |
| 95 | break; | |
| 96 | } | |
| 97 | ||
| 98 | return processor; | |
| 99 | } | |
| 100 | ||
| 101 | private Processor<String> createHTMLPreviewProcessor() { | |
| 102 | return new HTMLPreviewProcessor( getPreviewPane() ); | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Creates and links the processors at the end of the processing chain. | |
| 107 | * | |
| 108 | * @return A markdown, caret replacement, and preview pane processor chain. | |
| 109 | */ | |
| 110 | private Processor<String> createMarkdownProcessor() { | |
| 111 | final var hpp = createHTMLPreviewProcessor(); | |
| 112 | return new MarkdownProcessor( hpp, getPreviewPane().getPath() ); | |
| 113 | } | |
| 114 | ||
| 115 | protected Processor<String> createIdentityProcessor() { | |
| 116 | final var hpp = createHTMLPreviewProcessor(); | |
| 117 | return new IdentityProcessor( hpp ); | |
| 118 | } | |
| 119 | ||
| 120 | protected Processor<String> createDefinitionProcessor( | |
| 121 | final Processor<String> p ) { | |
| 122 | return new DefinitionProcessor( p, getResolvedMap() ); | |
| 123 | } | |
| 124 | ||
| 125 | protected Processor<String> createMarkdownDefinitionProcessor() { | |
| 126 | final var tpc = getCommonProcessor(); | |
| 127 | return createDefinitionProcessor( tpc ); | |
| 128 | } | |
| 129 | ||
| 130 | protected Processor<String> createXMLProcessor( final FileEditorTab tab ) { | |
| 131 | final var tpc = getCommonProcessor(); | |
| 132 | final var xmlp = new XMLProcessor( tpc, tab.getPath() ); | |
| 133 | return createDefinitionProcessor( xmlp ); | |
| 134 | } | |
| 135 | ||
| 136 | protected Processor<String> createRProcessor() { | |
| 137 | final var tpc = getCommonProcessor(); | |
| 138 | final var rp = new InlineRProcessor( tpc, getResolvedMap() ); | |
| 139 | return new RVariableProcessor( rp, getResolvedMap() ); | |
| 140 | } | |
| 141 | ||
| 142 | protected Processor<String> createRXMLProcessor( final FileEditorTab tab ) { | |
| 143 | final var tpc = getCommonProcessor(); | |
| 144 | final var xmlp = new XMLProcessor( tpc, tab.getPath() ); | |
| 145 | final var rp = new InlineRProcessor( xmlp, getResolvedMap() ); | |
| 146 | return new RVariableProcessor( rp, getResolvedMap() ); | |
| 147 | } | |
| 148 | ||
| 149 | private HTMLPreviewPane getPreviewPane() { | |
| 150 | return mPreviewPane; | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Returns the variable map of interpolated definitions. | |
| 155 | * | |
| 156 | * @return A map to help dereference variables. | |
| 157 | */ | |
| 158 | private Map<String, String> getResolvedMap() { | |
| 159 | return mResolvedMap; | |
| 160 | } | |
| 161 | ||
| 162 | /** | |
| 163 | * Returns a processor common to all processors: markdown, caret position | |
| 164 | * token replacer, and an HTML preview renderer. | |
| 165 | * | |
| 166 | * @return Processors at the end of the processing chain. | |
| 167 | */ | |
| 168 | private Processor<String> getCommonProcessor() { | |
| 169 | return mMarkdownProcessor; | |
| 170 | } | |
| 171 | } | |
| 1 | 172 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import java.util.HashMap; | |
| 31 | import java.util.Map; | |
| 32 | ||
| 33 | /** | |
| 34 | * Converts the keys of the resolved map from default form to R form, then | |
| 35 | * performs a substitution on the text. The default R variable syntax is | |
| 36 | * {@code v$tree$leaf}. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class RVariableProcessor extends DefinitionProcessor { | |
| 41 | ||
| 42 | public RVariableProcessor( | |
| 43 | final Processor<String> rp, final Map<String, String> map ) { | |
| 44 | super( rp, map ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the R-based version of the interpolated variable definitions. | |
| 49 | * | |
| 50 | * @return Variable names transmogrified from the default syntax to R syntax. | |
| 51 | */ | |
| 52 | @Override | |
| 53 | protected Map<String, String> getDefinitions() { | |
| 54 | return toR( super.getDefinitions() ); | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Converts the given map from regular variables to R variables. | |
| 59 | * | |
| 60 | * @param map Map of variable names to values. | |
| 61 | * @return Map of R variables. | |
| 62 | */ | |
| 63 | private Map<String, String> toR( final Map<String, String> map ) { | |
| 64 | final Map<String, String> rMap = new HashMap<>( map.size() ); | |
| 65 | ||
| 66 | for( final String key : map.keySet() ) { | |
| 67 | rMap.put( toRKey( key ), toRValue( map.get( key ) ) ); | |
| 68 | } | |
| 69 | ||
| 70 | return rMap; | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Transforms a variable name from $tree.branch.leaf$ to v$tree$branch$leaf | |
| 75 | * form. | |
| 76 | * | |
| 77 | * @param key The variable name to transform, can be empty but not null. | |
| 78 | * @return The transformed variable name. | |
| 79 | */ | |
| 80 | private String toRKey( final String key ) { | |
| 81 | // Replace all the periods with dollar symbols. | |
| 82 | final StringBuilder sb = new StringBuilder( 'v' + key ); | |
| 83 | final int length = sb.length(); | |
| 84 | ||
| 85 | // Replace all periods with dollar symbols. Normally we'd check i >= 0, | |
| 86 | // but the prepended 'v' is always going to be a 'v', not a dot. | |
| 87 | for( int i = length - 1; i > 0; i-- ) { | |
| 88 | if( sb.charAt( i ) == '.' ) { | |
| 89 | sb.setCharAt( i, '$' ); | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | // The length is always at least 1 (the 'v'), so bounds aren't broken here. | |
| 94 | sb.setLength( length - 1 ); | |
| 95 | ||
| 96 | return sb.toString(); | |
| 97 | } | |
| 98 | ||
| 99 | private String toRValue( final String value ) { | |
| 100 | return '\'' + escape( value, '\'', "\\'" ) + '\''; | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * TODO: Make generic method for replacing text. | |
| 105 | * | |
| 106 | * @param haystack Search this string for the needle, must not be null. | |
| 107 | * @param needle The character to find in the haystack. | |
| 108 | * @param thread Replace the needle with this text, if the needle is found. | |
| 109 | * @return The haystack with the all instances of needle replaced with thread. | |
| 110 | */ | |
| 111 | @SuppressWarnings("SameParameterValue") | |
| 112 | private String escape( | |
| 113 | final String haystack, final char needle, final String thread ) { | |
| 114 | int end = haystack.indexOf( needle ); | |
| 115 | ||
| 116 | if( end < 0 ) { | |
| 117 | return haystack; | |
| 118 | } | |
| 119 | ||
| 120 | final int length = haystack.length(); | |
| 121 | int start = 0; | |
| 122 | ||
| 123 | // Replace up to 32 occurrences before the string reallocates its buffer. | |
| 124 | final StringBuilder sb = new StringBuilder( length + 32 ); | |
| 125 | ||
| 126 | while( end >= 0 ) { | |
| 127 | sb.append( haystack, start, end ).append( thread ); | |
| 128 | start = end + 1; | |
| 129 | end = haystack.indexOf( needle, start ); | |
| 130 | } | |
| 131 | ||
| 132 | return sb.append( haystack.substring( start ) ).toString(); | |
| 133 | } | |
| 134 | } | |
| 1 | 135 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.Snitch; | |
| 32 | import net.sf.saxon.TransformerFactoryImpl; | |
| 33 | import net.sf.saxon.trans.XPathException; | |
| 34 | ||
| 35 | import javax.xml.stream.XMLEventReader; | |
| 36 | import javax.xml.stream.XMLInputFactory; | |
| 37 | import javax.xml.stream.XMLStreamException; | |
| 38 | import javax.xml.stream.events.ProcessingInstruction; | |
| 39 | import javax.xml.stream.events.XMLEvent; | |
| 40 | import javax.xml.transform.*; | |
| 41 | import javax.xml.transform.stream.StreamResult; | |
| 42 | import javax.xml.transform.stream.StreamSource; | |
| 43 | import java.io.File; | |
| 44 | import java.io.Reader; | |
| 45 | import java.io.StringReader; | |
| 46 | import java.io.StringWriter; | |
| 47 | import java.nio.file.Path; | |
| 48 | import java.nio.file.Paths; | |
| 49 | ||
| 50 | import static net.sf.saxon.tree.util.ProcInstParser.getPseudoAttribute; | |
| 51 | ||
| 52 | /** | |
| 53 | * Transforms an XML document. The XML document must have a stylesheet specified | |
| 54 | * as part of its processing instructions, such as: | |
| 55 | * | |
| 56 | * <code>xml-stylesheet type="text/xsl" href="markdown.xsl"</code> | |
| 57 | * <p> | |
| 58 | * The XSL must transform the XML document into Markdown, or another format | |
| 59 | * recognized by the next link on the chain. | |
| 60 | * | |
| 61 | * @author White Magic Software, Ltd. | |
| 62 | */ | |
| 63 | public class XMLProcessor extends AbstractProcessor<String> | |
| 64 | implements ErrorListener { | |
| 65 | ||
| 66 | private final Snitch snitch = Services.load( Snitch.class ); | |
| 67 | ||
| 68 | private XMLInputFactory xmlInputFactory; | |
| 69 | private TransformerFactory transformerFactory; | |
| 70 | private Transformer transformer; | |
| 71 | ||
| 72 | private Path path; | |
| 73 | ||
| 74 | /** | |
| 75 | * Constructs an XML processor that can transform an XML document into another | |
| 76 | * format based on the XSL file specified as a processing instruction. The | |
| 77 | * path must point to the directory where the XSL file is found, which implies | |
| 78 | * that they must be in the same directory. | |
| 79 | * | |
| 80 | * @param processor Next link in the processing chain. | |
| 81 | * @param path The path to the XML file content to be processed. | |
| 82 | */ | |
| 83 | public XMLProcessor( final Processor<String> processor, final Path path ) { | |
| 84 | super( processor ); | |
| 85 | setPath( path ); | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Transforms the given XML text into another form (typically Markdown). | |
| 90 | * | |
| 91 | * @param text The text to transform, can be empty, cannot be null. | |
| 92 | * @return The transformed text, or empty if text is empty. | |
| 93 | */ | |
| 94 | @Override | |
| 95 | public String processLink( final String text ) { | |
| 96 | try { | |
| 97 | return text.isEmpty() ? text : transform( text ); | |
| 98 | } catch( final Exception ex ) { | |
| 99 | throw new RuntimeException( ex ); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Performs an XSL transformation on the given XML text. The XML text must | |
| 105 | * have a processing instruction that points to the XSL template file to use | |
| 106 | * for the transformation. | |
| 107 | * | |
| 108 | * @param text The text to transform. | |
| 109 | * @return The transformed text. | |
| 110 | */ | |
| 111 | private String transform( final String text ) throws Exception { | |
| 112 | // Extract the XML stylesheet processing instruction. | |
| 113 | final String template = getXsltFilename( text ); | |
| 114 | final Path xsl = getXslPath( template ); | |
| 115 | ||
| 116 | try( | |
| 117 | final StringWriter output = new StringWriter( text.length() ); | |
| 118 | final StringReader input = new StringReader( text ) ) { | |
| 119 | ||
| 120 | // Listen for external file modification events. | |
| 121 | getSnitch().listen( xsl ); | |
| 122 | ||
| 123 | getTransformer( xsl ).transform( | |
| 124 | new StreamSource( input ), | |
| 125 | new StreamResult( output ) | |
| 126 | ); | |
| 127 | ||
| 128 | return output.toString(); | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Returns an XSL transformer ready to transform an XML document using the | |
| 134 | * XSLT file specified by the given path. If the path is already known then | |
| 135 | * this will return the associated transformer. | |
| 136 | * | |
| 137 | * @param xsl The path to an XSLT file. | |
| 138 | * @return A transformer that will transform XML documents using the given | |
| 139 | * XSLT file. | |
| 140 | * @throws TransformerConfigurationException Could not instantiate the | |
| 141 | * transformer. | |
| 142 | */ | |
| 143 | private Transformer getTransformer( final Path xsl ) | |
| 144 | throws TransformerConfigurationException { | |
| 145 | if( this.transformer == null ) { | |
| 146 | this.transformer = createTransformer( xsl ); | |
| 147 | } | |
| 148 | ||
| 149 | return this.transformer; | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Creates a configured transformer ready to run. | |
| 154 | * | |
| 155 | * @param xsl The stylesheet to use for transforming XML documents. | |
| 156 | * @return The edited XML document transformed into another format (usually | |
| 157 | * markdown). | |
| 158 | * @throws TransformerConfigurationException Could not create the transformer. | |
| 159 | */ | |
| 160 | protected Transformer createTransformer( final Path xsl ) | |
| 161 | throws TransformerConfigurationException { | |
| 162 | final Source xslt = new StreamSource( xsl.toFile() ); | |
| 163 | ||
| 164 | return getTransformerFactory().newTransformer( xslt ); | |
| 165 | } | |
| 166 | ||
| 167 | private Path getXslPath( final String filename ) { | |
| 168 | final Path xmlPath = getPath(); | |
| 169 | final File xmlDirectory = xmlPath.toFile().getParentFile(); | |
| 170 | ||
| 171 | return Paths.get( xmlDirectory.getPath(), filename ); | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Given XML text, this will use a StAX pull reader to obtain the XML | |
| 176 | * stylesheet processing instruction. This will throw a parse exception if the | |
| 177 | * href pseudo-attribute filename value cannot be found. | |
| 178 | * | |
| 179 | * @param xml The XML containing an xml-stylesheet processing instruction. | |
| 180 | * @return The href pseudo-attribute value. | |
| 181 | * @throws XMLStreamException Could not parse the XML file. | |
| 182 | */ | |
| 183 | private String getXsltFilename( final String xml ) | |
| 184 | throws XMLStreamException, XPathException { | |
| 185 | ||
| 186 | String result = ""; | |
| 187 | ||
| 188 | try( final StringReader sr = new StringReader( xml ) ) { | |
| 189 | boolean found = false; | |
| 190 | int count = 0; | |
| 191 | final XMLEventReader reader = createXMLEventReader( sr ); | |
| 192 | ||
| 193 | // If the processing instruction wasn't found in the first 10 lines, | |
| 194 | // fail fast. This should iterate twice through the loop. | |
| 195 | while( !found && reader.hasNext() && count++ < 10 ) { | |
| 196 | final XMLEvent event = reader.nextEvent(); | |
| 197 | ||
| 198 | if( event.isProcessingInstruction() ) { | |
| 199 | final ProcessingInstruction pi = (ProcessingInstruction) event; | |
| 200 | final String target = pi.getTarget(); | |
| 201 | ||
| 202 | if( "xml-stylesheet".equalsIgnoreCase( target ) ) { | |
| 203 | result = getPseudoAttribute( pi.getData(), "href" ); | |
| 204 | found = true; | |
| 205 | } | |
| 206 | } | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | return result; | |
| 211 | } | |
| 212 | ||
| 213 | private XMLEventReader createXMLEventReader( final Reader reader ) | |
| 214 | throws XMLStreamException { | |
| 215 | return getXMLInputFactory().createXMLEventReader( reader ); | |
| 216 | } | |
| 217 | ||
| 218 | private synchronized XMLInputFactory getXMLInputFactory() { | |
| 219 | if( this.xmlInputFactory == null ) { | |
| 220 | this.xmlInputFactory = createXMLInputFactory(); | |
| 221 | } | |
| 222 | ||
| 223 | return this.xmlInputFactory; | |
| 224 | } | |
| 225 | ||
| 226 | private XMLInputFactory createXMLInputFactory() { | |
| 227 | return XMLInputFactory.newInstance(); | |
| 228 | } | |
| 229 | ||
| 230 | private synchronized TransformerFactory getTransformerFactory() { | |
| 231 | if( this.transformerFactory == null ) { | |
| 232 | this.transformerFactory = createTransformerFactory(); | |
| 233 | } | |
| 234 | ||
| 235 | return this.transformerFactory; | |
| 236 | } | |
| 237 | ||
| 238 | /** | |
| 239 | * Returns a high-performance XSLT 2 transformation engine. | |
| 240 | * | |
| 241 | * @return An XSL transforming engine. | |
| 242 | */ | |
| 243 | private TransformerFactory createTransformerFactory() { | |
| 244 | final TransformerFactory factory = new TransformerFactoryImpl(); | |
| 245 | ||
| 246 | // Bubble problems up to the user interface, rather than standard error. | |
| 247 | factory.setErrorListener( this ); | |
| 248 | ||
| 249 | return factory; | |
| 250 | } | |
| 251 | ||
| 252 | /** | |
| 253 | * Called when the XSL transformer issues a warning. | |
| 254 | * | |
| 255 | * @param ex The problem the transformer encountered. | |
| 256 | */ | |
| 257 | @Override | |
| 258 | public void warning( final TransformerException ex ) { | |
| 259 | throw new RuntimeException( ex ); | |
| 260 | } | |
| 261 | ||
| 262 | /** | |
| 263 | * Called when the XSL transformer issues an error. | |
| 264 | * | |
| 265 | * @param ex The problem the transformer encountered. | |
| 266 | */ | |
| 267 | @Override | |
| 268 | public void error( final TransformerException ex ) { | |
| 269 | throw new RuntimeException( ex ); | |
| 270 | } | |
| 271 | ||
| 272 | /** | |
| 273 | * Called when the XSL transformer issues a fatal error, which is probably | |
| 274 | * a bit over-dramatic a method name. | |
| 275 | * | |
| 276 | * @param ex The problem the transformer encountered. | |
| 277 | */ | |
| 278 | @Override | |
| 279 | public void fatalError( final TransformerException ex ) { | |
| 280 | throw new RuntimeException( ex ); | |
| 281 | } | |
| 282 | ||
| 283 | private void setPath( final Path path ) { | |
| 284 | this.path = path; | |
| 285 | } | |
| 286 | ||
| 287 | private Path getPath() { | |
| 288 | return this.path; | |
| 289 | } | |
| 290 | ||
| 291 | private Snitch getSnitch() { | |
| 292 | return this.snitch; | |
| 293 | } | |
| 294 | } | |
| 1 | 295 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.preferences.UserPreferences; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import com.scrivenvar.service.events.Notifier; | |
| 34 | import com.scrivenvar.util.ProtocolResolver; | |
| 35 | import com.vladsch.flexmark.ast.Image; | |
| 36 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 37 | import com.vladsch.flexmark.html.IndependentLinkResolverFactory; | |
| 38 | import com.vladsch.flexmark.html.LinkResolver; | |
| 39 | import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext; | |
| 40 | import com.vladsch.flexmark.html.renderer.LinkStatus; | |
| 41 | import com.vladsch.flexmark.html.renderer.ResolvedLink; | |
| 42 | import com.vladsch.flexmark.util.ast.Node; | |
| 43 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 44 | import org.jetbrains.annotations.NotNull; | |
| 45 | import org.renjin.repackaged.guava.base.Splitter; | |
| 46 | ||
| 47 | import java.io.File; | |
| 48 | import java.nio.file.Path; | |
| 49 | ||
| 50 | import static java.lang.String.format; | |
| 51 | ||
| 52 | /** | |
| 53 | * Responsible for ensuring that images can be rendered relative to a path. | |
| 54 | * This allows images to be located virtually anywhere. | |
| 55 | * | |
| 56 | * @author White Magic Software, Ltd. | |
| 57 | */ | |
| 58 | public class ImageLinkExtension implements HtmlRenderer.HtmlRendererExtension { | |
| 59 | private final static Options sOptions = Services.load( Options.class ); | |
| 60 | private final static Notifier sNotifier = Services.load( Notifier.class ); | |
| 61 | ||
| 62 | /** | |
| 63 | * Creates an extension capable of using a relative path to embed images. | |
| 64 | * | |
| 65 | * @param path The {@link Path} to the file being edited; the parent path | |
| 66 | * is the starting location of the relative image directory. | |
| 67 | * @return The new {@link ImageLinkExtension}, never {@code null}. | |
| 68 | */ | |
| 69 | public static ImageLinkExtension create( final Path path ) { | |
| 70 | return new ImageLinkExtension( path ); | |
| 71 | } | |
| 72 | ||
| 73 | private class Factory extends IndependentLinkResolverFactory { | |
| 74 | @Override | |
| 75 | public @NotNull LinkResolver apply( | |
| 76 | @NotNull final LinkResolverBasicContext context ) { | |
| 77 | return new ImageLinkResolver(); | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | private class ImageLinkResolver implements LinkResolver { | |
| 82 | private final UserPreferences mUserPref = getUserPreferences(); | |
| 83 | private final String mImagePrefix = | |
| 84 | mUserPref.getImagesDirectory().toString(); | |
| 85 | private final String mImageSuffixes = mUserPref.getImagesOrder(); | |
| 86 | ||
| 87 | public ImageLinkResolver() { | |
| 88 | } | |
| 89 | ||
| 90 | // you can also set/clear/modify attributes through | |
| 91 | // ResolvedLink.getAttributes() and | |
| 92 | // ResolvedLink.getNonNullAttributes() | |
| 93 | @NotNull | |
| 94 | @Override | |
| 95 | public ResolvedLink resolveLink( | |
| 96 | @NotNull final Node node, | |
| 97 | @NotNull final LinkResolverBasicContext context, | |
| 98 | @NotNull final ResolvedLink link ) { | |
| 99 | return node instanceof Image ? resolve( link ) : link; | |
| 100 | } | |
| 101 | ||
| 102 | @NotNull | |
| 103 | private ResolvedLink resolve( @NotNull final ResolvedLink link ) { | |
| 104 | String url = link.getUrl(); | |
| 105 | ||
| 106 | try { | |
| 107 | final String imageFile = format( "%s/%s", getImagePrefix(), url ); | |
| 108 | final String suffixes = getImageSuffixes(); | |
| 109 | final String editDir = getEditDirectory(); | |
| 110 | ||
| 111 | for( final String ext : Splitter.on( ' ' ).split( suffixes ) ) { | |
| 112 | final String imagePath = format( | |
| 113 | "%s/%s.%s", editDir, imageFile, ext ); | |
| 114 | final File file = new File( imagePath ); | |
| 115 | ||
| 116 | if( file.exists() ) { | |
| 117 | url = file.toString(); | |
| 118 | break; | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | final String protocol = ProtocolResolver.getProtocol( url ); | |
| 123 | if( "file".equals( protocol ) ) { | |
| 124 | url = "file://" + url; | |
| 125 | } | |
| 126 | ||
| 127 | return link.withStatus( LinkStatus.VALID ).withUrl( url ); | |
| 128 | } catch( final Exception e ) { | |
| 129 | getNotifier().notify( e ); | |
| 130 | } | |
| 131 | ||
| 132 | return link; | |
| 133 | } | |
| 134 | ||
| 135 | private String getImagePrefix() { | |
| 136 | return mImagePrefix; | |
| 137 | } | |
| 138 | ||
| 139 | private String getImageSuffixes() { | |
| 140 | return mImageSuffixes; | |
| 141 | } | |
| 142 | ||
| 143 | private String getEditDirectory() { | |
| 144 | return mPath.getParent().toString(); | |
| 145 | } | |
| 146 | } | |
| 147 | ||
| 148 | private final Path mPath; | |
| 149 | ||
| 150 | private ImageLinkExtension( final Path path ) { | |
| 151 | mPath = path; | |
| 152 | } | |
| 153 | ||
| 154 | @Override | |
| 155 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 156 | } | |
| 157 | ||
| 158 | @Override | |
| 159 | public void extend( | |
| 160 | final HtmlRenderer.Builder rendererBuilder, | |
| 161 | @NotNull final String rendererType ) { | |
| 162 | rendererBuilder.linkResolverFactory( new Factory() ); | |
| 163 | } | |
| 164 | ||
| 165 | private UserPreferences getUserPreferences() { | |
| 166 | return getOptions().getUserPreferences(); | |
| 167 | } | |
| 168 | ||
| 169 | private Options getOptions() { | |
| 170 | return sOptions; | |
| 171 | } | |
| 172 | ||
| 173 | private Notifier getNotifier() { | |
| 174 | return sNotifier; | |
| 175 | } | |
| 176 | } | |
| 1 | 177 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.processors.AbstractProcessor; | |
| 31 | import com.scrivenvar.processors.Processor; | |
| 32 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; | |
| 33 | import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; | |
| 34 | import com.vladsch.flexmark.ext.tables.TablesExtension; | |
| 35 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 36 | import com.vladsch.flexmark.parser.Parser; | |
| 37 | import com.vladsch.flexmark.util.ast.IParse; | |
| 38 | import com.vladsch.flexmark.util.ast.Node; | |
| 39 | import com.vladsch.flexmark.util.misc.Extension; | |
| 40 | ||
| 41 | import java.nio.file.Path; | |
| 42 | import java.util.ArrayList; | |
| 43 | import java.util.Collection; | |
| 44 | ||
| 45 | import static com.scrivenvar.Constants.USER_DIRECTORY; | |
| 46 | ||
| 47 | /** | |
| 48 | * Responsible for parsing a Markdown document and rendering it as HTML. | |
| 49 | * | |
| 50 | * @author White Magic Software, Ltd. | |
| 51 | */ | |
| 52 | public class MarkdownProcessor extends AbstractProcessor<String> { | |
| 53 | ||
| 54 | private final HtmlRenderer mRenderer; | |
| 55 | private final IParse mParser; | |
| 56 | ||
| 57 | public MarkdownProcessor( | |
| 58 | final Processor<String> successor ) { | |
| 59 | this( successor, Path.of( USER_DIRECTORY ) ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Constructs a new Markdown processor that can create HTML documents. | |
| 64 | * | |
| 65 | * @param successor Usually the HTML Preview Processor. | |
| 66 | */ | |
| 67 | public MarkdownProcessor( | |
| 68 | final Processor<String> successor, final Path path ) { | |
| 69 | super( successor ); | |
| 70 | ||
| 71 | final Collection<Extension> extensions = new ArrayList<>(); | |
| 72 | extensions.add( TablesExtension.create() ); | |
| 73 | extensions.add( SuperscriptExtension.create() ); | |
| 74 | extensions.add( StrikethroughSubscriptExtension.create() ); | |
| 75 | extensions.add( ImageLinkExtension.create( path ) ); | |
| 76 | ||
| 77 | mRenderer = HtmlRenderer.builder().extensions( extensions ).build(); | |
| 78 | mParser = Parser.builder().extensions( extensions ).build(); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Converts the given Markdown string into HTML, without the doctype, html, | |
| 83 | * head, and body tags. | |
| 84 | * | |
| 85 | * @param markdown The string to convert from Markdown to HTML. | |
| 86 | * @return The HTML representation of the Markdown document. | |
| 87 | */ | |
| 88 | @Override | |
| 89 | public String processLink( final String markdown ) { | |
| 90 | return toHtml( markdown ); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Returns the AST in the form of a node for the given markdown document. This | |
| 95 | * can be used, for example, to determine if a hyperlink exists inside of a | |
| 96 | * paragraph. | |
| 97 | * | |
| 98 | * @param markdown The markdown to convert into an AST. | |
| 99 | * @return The markdown AST for the given text (usually a paragraph). | |
| 100 | */ | |
| 101 | public Node toNode( final String markdown ) { | |
| 102 | return parse( markdown ); | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Helper method to create an AST given some markdown. | |
| 107 | * | |
| 108 | * @param markdown The markdown to parse. | |
| 109 | * @return The root node of the markdown tree. | |
| 110 | */ | |
| 111 | private Node parse( final String markdown ) { | |
| 112 | return getParser().parse( markdown ); | |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Converts a string of markdown into HTML. | |
| 117 | * | |
| 118 | * @param markdown The markdown text to convert to HTML, must not be null. | |
| 119 | * @return The markdown rendered as an HTML document. | |
| 120 | */ | |
| 121 | private String toHtml( final String markdown ) { | |
| 122 | return getRenderer().render( parse( markdown ) ); | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Creates the Markdown document processor. | |
| 127 | * | |
| 128 | * @return A Parser that can build an abstract syntax tree. | |
| 129 | */ | |
| 130 | private IParse getParser() { | |
| 131 | return mParser; | |
| 132 | } | |
| 133 | ||
| 134 | private HtmlRenderer getRenderer() { | |
| 135 | return mRenderer; | |
| 136 | } | |
| 137 | } | |
| 1 | 138 |
| 1 | /* | |
| 2 | * The MIT License | |
| 3 | * | |
| 4 | * Copyright 2016 . | |
| 5 | * | |
| 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 7 | * of this software and associated documentation files (the "Software"), to deal | |
| 8 | * in the Software without restriction, including without limitation the rights | |
| 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 10 | * copies of the Software, and to permit persons to whom the Software is | |
| 11 | * furnished to do so, subject to the following conditions: | |
| 12 | * | |
| 13 | * The above copyright notice and this permission notice shall be included in | |
| 14 | * all copies or substantial portions of the Software. | |
| 15 | * | |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 22 | * THE SOFTWARE. | |
| 23 | */ | |
| 24 | package com.scrivenvar.processors.text; | |
| 25 | ||
| 26 | import java.util.Map; | |
| 27 | ||
| 28 | /** | |
| 29 | * Responsible for common behaviour across all text replacer implementations. | |
| 30 | * | |
| 31 | * @author White Magic Software, Ltd. | |
| 32 | */ | |
| 33 | public abstract class AbstractTextReplacer implements TextReplacer { | |
| 34 | ||
| 35 | /** | |
| 36 | * Default (empty) constructor. | |
| 37 | */ | |
| 38 | protected AbstractTextReplacer() { | |
| 39 | } | |
| 40 | ||
| 41 | protected String[] keys( final Map<String, String> map ) { | |
| 42 | return map.keySet().toArray( new String[ 0 ] ); | |
| 43 | } | |
| 44 | ||
| 45 | protected String[] values( final Map<String, String> map ) { | |
| 46 | return map.values().toArray( new String[ 0 ] ); | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | import org.ahocorasick.trie.Emit; | |
| 32 | import org.ahocorasick.trie.Trie.TrieBuilder; | |
| 33 | import static org.ahocorasick.trie.Trie.builder; | |
| 34 | ||
| 35 | /** | |
| 36 | * Replaces text using an Aho-Corasick algorithm. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class AhoCorasickReplacer extends AbstractTextReplacer { | |
| 41 | ||
| 42 | /** | |
| 43 | * Default (empty) constructor. | |
| 44 | */ | |
| 45 | protected AhoCorasickReplacer() { | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public String replace( final String text, final Map<String, String> map ) { | |
| 50 | // Create a buffer sufficiently large that re-allocations are minimized. | |
| 51 | final StringBuilder sb = new StringBuilder( (int)(text.length() * 1.25) ); | |
| 52 | ||
| 53 | // The TrieBuilder should only match whole words and ignore overlaps (there | |
| 54 | // shouldn't be any). | |
| 55 | final TrieBuilder builder = builder().onlyWholeWords().ignoreOverlaps(); | |
| 56 | ||
| 57 | for( final String key : keys( map ) ) { | |
| 58 | builder.addKeyword( key ); | |
| 59 | } | |
| 60 | ||
| 61 | int index = 0; | |
| 62 | ||
| 63 | // Replace all instances with dereferenced variables. | |
| 64 | for( final Emit emit : builder.build().parseText( text ) ) { | |
| 65 | sb.append( text, index, emit.getStart() ); | |
| 66 | sb.append( map.get( emit.getKeyword() ) ); | |
| 67 | index = emit.getEnd() + 1; | |
| 68 | } | |
| 69 | ||
| 70 | // Add the remainder of the string (contains no more matches). | |
| 71 | sb.append( text.substring( index ) ); | |
| 72 | ||
| 73 | return sb.toString(); | |
| 74 | } | |
| 75 | } | |
| 1 | 76 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | import static org.apache.commons.lang3.StringUtils.replaceEach; | |
| 33 | ||
| 34 | /** | |
| 35 | * Replaces text using Apache's StringUtils.replaceEach method. | |
| 36 | * | |
| 37 | * @author White Magic Software, Ltd. | |
| 38 | */ | |
| 39 | public class StringUtilsReplacer extends AbstractTextReplacer { | |
| 40 | ||
| 41 | /** | |
| 42 | * Default (empty) constructor. | |
| 43 | */ | |
| 44 | protected StringUtilsReplacer() { | |
| 45 | } | |
| 46 | ||
| 47 | @Override | |
| 48 | public String replace( final String text, final Map<String, String> map ) { | |
| 49 | return replaceEach( text, keys( map ), values( map ) ); | |
| 50 | } | |
| 51 | } | |
| 1 | 52 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | /** | |
| 33 | * Used to generate a class capable of efficiently replacing variable | |
| 34 | * definitions with their values. | |
| 35 | * | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public final class TextReplacementFactory { | |
| 39 | ||
| 40 | private final static TextReplacer APACHE = new StringUtilsReplacer(); | |
| 41 | private final static TextReplacer AHO_CORASICK = new AhoCorasickReplacer(); | |
| 42 | ||
| 43 | /** | |
| 44 | * Returns a text search/replacement instance that is reasonably optimal for | |
| 45 | * the given length of text. | |
| 46 | * | |
| 47 | * @param length The length of text that requires some search and replacing. | |
| 48 | * | |
| 49 | * @return A class that can search and replace text with utmost expediency. | |
| 50 | */ | |
| 51 | public static TextReplacer getTextReplacer( final int length ) { | |
| 52 | // After about 1,500 characters, the StringUtils implementation is less | |
| 53 | // performant than the Aho-Corsick implementation. | |
| 54 | // | |
| 55 | // See http://stackoverflow.com/a/40836618/59087 | |
| 56 | return length < 1500 ? APACHE : AHO_CORASICK; | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Convenience method to instantiate a suitable text replacer algorithm and | |
| 61 | * perform a replacement using the given map. At this point, the values should | |
| 62 | * be already dereferenced and ready to be substituted verbatim; any | |
| 63 | * recursively defined values must have been interpolated previously. | |
| 64 | * | |
| 65 | * @param text The text containing zero or more variables to replace. | |
| 66 | * @param map The map of variables to their dereferenced values. | |
| 67 | * | |
| 68 | * @return The text with all variables replaced. | |
| 69 | */ | |
| 70 | public static String replace( | |
| 71 | final String text, final Map<String, String> map ) { | |
| 72 | return getTextReplacer( text.length() ).replace( text, map ); | |
| 73 | } | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | /** | |
| 33 | * Defines the ability to replace text given a set of keys and values. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public interface TextReplacer { | |
| 38 | ||
| 39 | /** | |
| 40 | * Searches through the given text for any of the keys given in the map and | |
| 41 | * replaces the keys that appear in the text with the key's corresponding | |
| 42 | * value. | |
| 43 | * | |
| 44 | * @param text The text that contains zero or more keys. | |
| 45 | * @param map The set of keys mapped to replacement values. | |
| 46 | * @return The given text with all keys replaced with corresponding values. | |
| 47 | */ | |
| 48 | String replace( String text, Map<String, String> map ); | |
| 49 | } | |
| 1 | 50 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import com.scrivenvar.preferences.UserPreferences; | |
| 31 | ||
| 32 | import java.util.prefs.BackingStoreException; | |
| 33 | import java.util.prefs.Preferences; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for persisting options. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public interface Options extends Service { | |
| 41 | ||
| 42 | /** | |
| 43 | * Returns a reference to the persistent settings that may be configured | |
| 44 | * through the UI. | |
| 45 | * | |
| 46 | * @return A valid {@link UserPreferences} instance, never {@code null}. | |
| 47 | */ | |
| 48 | UserPreferences getUserPreferences(); | |
| 49 | ||
| 50 | /** | |
| 51 | * Returns the {@link Preferences} that persist settings that cannot | |
| 52 | * be configured via the user interface. | |
| 53 | * | |
| 54 | * @return A valid {@link Preferences} instance, never {@code null}. | |
| 55 | */ | |
| 56 | Preferences getState(); | |
| 57 | ||
| 58 | /** | |
| 59 | * Stores the key and value into the user preferences to be loaded the next | |
| 60 | * time the application is launched. | |
| 61 | * | |
| 62 | * @param key Name of the key to persist along with its value. | |
| 63 | * @param value Value to associate with the key. | |
| 64 | * @throws BackingStoreException Could not persist the change. | |
| 65 | */ | |
| 66 | void put( String key, String value ) throws BackingStoreException; | |
| 67 | ||
| 68 | /** | |
| 69 | * Retrieves the value for a key in the user preferences. | |
| 70 | * | |
| 71 | * @param key Retrieve the value of this key. | |
| 72 | * @param defaultValue The value to return in the event that the given key has | |
| 73 | * no associated value. | |
| 74 | * @return The value associated with the key. | |
| 75 | */ | |
| 76 | String get( String key, String defaultValue ); | |
| 77 | ||
| 78 | /** | |
| 79 | * Retrieves the value for a key in the user preferences. This will return | |
| 80 | * the empty string if the value cannot be found. | |
| 81 | * | |
| 82 | * @param key The key to find in the preferences. | |
| 83 | * @return A non-null, possibly empty value for the key. | |
| 84 | */ | |
| 85 | String get( String key ); | |
| 86 | } | |
| 1 | 87 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | /** | |
| 31 | * All services inherit from this one. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public interface Service { | |
| 36 | } | |
| 1 | 37 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import java.util.Iterator; | |
| 31 | import java.util.List; | |
| 32 | ||
| 33 | /** | |
| 34 | * Defines how settings and options can be retrieved. | |
| 35 | * | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public interface Settings extends Service { | |
| 39 | ||
| 40 | /** | |
| 41 | * Returns a setting property or its default value. | |
| 42 | * | |
| 43 | * @param property The property key name to obtain its value. | |
| 44 | * @param defaultValue The default value to return iff the property cannot be | |
| 45 | * found. | |
| 46 | * @return The property value for the given property key. | |
| 47 | */ | |
| 48 | String getSetting( String property, String defaultValue ); | |
| 49 | ||
| 50 | /** | |
| 51 | * Returns a setting property or its default value. | |
| 52 | * | |
| 53 | * @param property The property key name to obtain its value. | |
| 54 | * @param defaultValue The default value to return iff the property cannot be | |
| 55 | * found. | |
| 56 | * @return The property value for the given property key. | |
| 57 | */ | |
| 58 | int getSetting( String property, int defaultValue ); | |
| 59 | ||
| 60 | /** | |
| 61 | * Returns a list of property names that begin with the given prefix. The | |
| 62 | * prefix is included in any matching results. This will return keys that | |
| 63 | * either match the prefix or start with the prefix followed by a dot ('.'). | |
| 64 | * For example, a prefix value of <code>the.property.name</code> will likely | |
| 65 | * return the expected results, but <code>the.property.name.</code> (note the | |
| 66 | * extraneous period) will probably not. | |
| 67 | * | |
| 68 | * @param prefix The prefix to compare against each property name. | |
| 69 | * @return The list of property names that have the given prefix. | |
| 70 | */ | |
| 71 | Iterator<String> getKeys( final String prefix ); | |
| 72 | ||
| 73 | /** | |
| 74 | * Convert the generic list of property objects into strings. | |
| 75 | * | |
| 76 | * @param property The property value to coerce. | |
| 77 | * @param defaults The defaults values to use should the property be unset. | |
| 78 | * @return The list of properties coerced from objects to strings. | |
| 79 | */ | |
| 80 | List<String> getStringSettingList( String property, List<String> defaults ); | |
| 81 | ||
| 82 | /** | |
| 83 | * Converts the generic list of property objects into strings. | |
| 84 | * | |
| 85 | * @param property The property value to coerce. | |
| 86 | * @return The list of properties coerced from objects to strings. | |
| 87 | */ | |
| 88 | List<String> getStringSettingList( String property ); | |
| 89 | ||
| 90 | /** | |
| 91 | * Changes key's value. This will clear the old value before setting the | |
| 92 | * new value so that the old value is erased, not changed into a list. | |
| 93 | * | |
| 94 | * @param key The property key name to obtain its value. | |
| 95 | * @param value The new value to set. | |
| 96 | */ | |
| 97 | void putSetting( String key, String value ); | |
| 98 | } | |
| 1 | 99 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import java.io.IOException; | |
| 31 | import java.nio.file.Path; | |
| 32 | import java.util.Observer; | |
| 33 | ||
| 34 | /** | |
| 35 | * Listens for changes to file system files and directories. | |
| 36 | * | |
| 37 | * @author White Magic Software, Ltd. | |
| 38 | */ | |
| 39 | public interface Snitch extends Service, Runnable { | |
| 40 | ||
| 41 | /** | |
| 42 | * Adds an observer to the set of observers for this object, provided that it | |
| 43 | * is not the same as some observer already in the set. The order in which | |
| 44 | * notifications will be delivered to multiple observers is not specified. | |
| 45 | * | |
| 46 | * @param o The object to receive changed events for when monitored files | |
| 47 | * are changed. | |
| 48 | */ | |
| 49 | void addObserver( Observer o ); | |
| 50 | ||
| 51 | /** | |
| 52 | * Listens for changes to the path. If the path specifies a file, then only | |
| 53 | * notifications pertaining to that file are sent. Otherwise, change events | |
| 54 | * for the directory that contains the file are sent. This method must allow | |
| 55 | * for multiple calls to the same file without incurring additional listeners | |
| 56 | * or events. | |
| 57 | * | |
| 58 | * @param file Send notifications when this file changes, can be null. | |
| 59 | * @throws IOException Couldn't create a watcher for the given file. | |
| 60 | */ | |
| 61 | void listen( Path file ) throws IOException; | |
| 62 | ||
| 63 | /** | |
| 64 | * Removes the given file from the notifications list. | |
| 65 | * | |
| 66 | * @param file The file to stop monitoring for any changes, can be null. | |
| 67 | */ | |
| 68 | void ignore( Path file ); | |
| 69 | ||
| 70 | /** | |
| 71 | * Stop listening for events. | |
| 72 | */ | |
| 73 | void stop(); | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents a message that contains a title and content. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public interface Notification { | |
| 36 | ||
| 37 | /** | |
| 38 | * Alert title. | |
| 39 | * | |
| 40 | * @return A non-null string to use as alert message title. | |
| 41 | */ | |
| 42 | String getTitle(); | |
| 43 | ||
| 44 | /** | |
| 45 | * Alert message content. | |
| 46 | * | |
| 47 | * @return A non-null string that contains information for the user. | |
| 48 | */ | |
| 49 | String getContent(); | |
| 50 | } | |
| 1 | 51 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events; | |
| 29 | ||
| 30 | import javafx.scene.control.Alert; | |
| 31 | import javafx.scene.control.ButtonType; | |
| 32 | import javafx.stage.Window; | |
| 33 | ||
| 34 | import java.io.File; | |
| 35 | import java.io.FileWriter; | |
| 36 | import java.io.IOException; | |
| 37 | import java.io.PrintWriter; | |
| 38 | import java.util.Observer; | |
| 39 | ||
| 40 | /** | |
| 41 | * Provides the application with a uniform way to notify the user of events. | |
| 42 | * | |
| 43 | * @author White Magic Software, Ltd. | |
| 44 | */ | |
| 45 | public interface Notifier { | |
| 46 | ||
| 47 | ButtonType YES = ButtonType.YES; | |
| 48 | ButtonType NO = ButtonType.NO; | |
| 49 | ButtonType CANCEL = ButtonType.CANCEL; | |
| 50 | ||
| 51 | /** | |
| 52 | * Notifies the user of a problem. | |
| 53 | * | |
| 54 | * @param message The problem description. | |
| 55 | */ | |
| 56 | void notify( final String message ); | |
| 57 | ||
| 58 | /** | |
| 59 | * Notifies the user about the exception. | |
| 60 | * | |
| 61 | * @param ex The exception containing a message to show to the user. | |
| 62 | */ | |
| 63 | default void notify( final Exception ex ) { | |
| 64 | assert ex != null; | |
| 65 | ||
| 66 | log( ex ); | |
| 67 | notify( ex.getMessage() ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Writes the exception to a log file. The log file should be written | |
| 72 | * in the System's temporary directory. | |
| 73 | * | |
| 74 | * @param ex The exception to show in the status bar and log to a file. | |
| 75 | */ | |
| 76 | default void log( final Exception ex ) { | |
| 77 | try( | |
| 78 | final FileWriter fw = new FileWriter( getLogPath(), true ); | |
| 79 | final PrintWriter pw = new PrintWriter( fw ) | |
| 80 | ) { | |
| 81 | ex.printStackTrace( pw ); | |
| 82 | } catch( final IOException ioe ) { | |
| 83 | // The notify method will display the message on the status | |
| 84 | // bar. | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Returns the fully qualified path to the log file to write to when | |
| 90 | * an exception occurs. | |
| 91 | * | |
| 92 | * @return Location of the log file for writing unexpected exceptions. | |
| 93 | */ | |
| 94 | File getLogPath(); | |
| 95 | ||
| 96 | /** | |
| 97 | * Causes any displayed notifications to disappear. | |
| 98 | */ | |
| 99 | void clear(); | |
| 100 | ||
| 101 | /** | |
| 102 | * Constructs a default alert message text for a modal alert dialog. | |
| 103 | * | |
| 104 | * @param title The dialog box message title. | |
| 105 | * @param message The dialog box message content (needs formatting). | |
| 106 | * @param args The arguments to the message content that must be formatted. | |
| 107 | * @return The message suitable for building a modal alert dialog. | |
| 108 | */ | |
| 109 | Notification createNotification( | |
| 110 | String title, | |
| 111 | String message, | |
| 112 | Object... args ); | |
| 113 | ||
| 114 | /** | |
| 115 | * Creates an alert of alert type error with a message showing the cause of | |
| 116 | * the error. | |
| 117 | * | |
| 118 | * @param parent Dialog box owner (for modal purposes). | |
| 119 | * @param message The error message, title, and possibly more details. | |
| 120 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 121 | */ | |
| 122 | Alert createError( Window parent, Notification message ); | |
| 123 | ||
| 124 | /** | |
| 125 | * Creates an alert of alert type confirmation with Yes/No/Cancel buttons. | |
| 126 | * | |
| 127 | * @param parent Dialog box owner (for modal purposes). | |
| 128 | * @param message The message, title, and possibly more details. | |
| 129 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 130 | */ | |
| 131 | Alert createConfirmation( Window parent, Notification message ); | |
| 132 | ||
| 133 | /** | |
| 134 | * Adds an observer to the list of objects that receive notifications about | |
| 135 | * error messages to be presented to the user. | |
| 136 | * | |
| 137 | * @param observer The observer instance to notify. | |
| 138 | */ | |
| 139 | void addObserver( Observer observer ); | |
| 140 | } | |
| 1 | 141 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.Settings; | |
| 32 | import javafx.scene.Node; | |
| 33 | import javafx.scene.control.ButtonBar; | |
| 34 | import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS; | |
| 35 | import javafx.scene.control.DialogPane; | |
| 36 | ||
| 37 | /** | |
| 38 | * Ensures a consistent button order for alert dialogs across platforms (because | |
| 39 | * the default button order on Linux defies all logic). | |
| 40 | * | |
| 41 | * @author White Magic Software, Ltd. | |
| 42 | */ | |
| 43 | public class ButtonOrderPane extends DialogPane { | |
| 44 | ||
| 45 | @Override | |
| 46 | protected Node createButtonBar() { | |
| 47 | final ButtonBar node = (ButtonBar)super.createButtonBar(); | |
| 48 | node.setButtonOrder( getButtonOrder() ); | |
| 49 | return node; | |
| 50 | } | |
| 51 | ||
| 52 | private String getButtonOrder() { | |
| 53 | return getSetting( "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS ); | |
| 54 | } | |
| 55 | ||
| 56 | @SuppressWarnings("SameParameterValue") | |
| 57 | private String getSetting( final String key, final String defaultValue ) { | |
| 58 | return getSettings().getSetting( key, defaultValue ); | |
| 59 | } | |
| 60 | ||
| 61 | private Settings getSettings() { | |
| 62 | return Services.load( Settings.class ); | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | ||
| 32 | import java.text.MessageFormat; | |
| 33 | ||
| 34 | /** | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class DefaultNotification implements Notification { | |
| 38 | ||
| 39 | private final String title; | |
| 40 | private final String content; | |
| 41 | ||
| 42 | /** | |
| 43 | * Constructs default message text for a notification. | |
| 44 | * | |
| 45 | * @param title The message title. | |
| 46 | * @param message The message content (needs formatting). | |
| 47 | * @param args The arguments to the message content that must be formatted. | |
| 48 | */ | |
| 49 | public DefaultNotification( | |
| 50 | final String title, | |
| 51 | final String message, | |
| 52 | final Object... args ) { | |
| 53 | this.title = title; | |
| 54 | this.content = MessageFormat.format( message, args ); | |
| 55 | } | |
| 56 | ||
| 57 | @Override | |
| 58 | public String getTitle() { | |
| 59 | return this.title; | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public String getContent() { | |
| 64 | return this.content; | |
| 65 | } | |
| 66 | } | |
| 1 | 67 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | import javafx.scene.control.Alert; | |
| 33 | import javafx.scene.control.Alert.AlertType; | |
| 34 | import javafx.stage.Window; | |
| 35 | ||
| 36 | import java.io.File; | |
| 37 | import java.nio.file.Paths; | |
| 38 | import java.util.Observable; | |
| 39 | ||
| 40 | import static com.scrivenvar.Constants.APP_TITLE; | |
| 41 | import static com.scrivenvar.Constants.STATUS_BAR_OK; | |
| 42 | import static com.scrivenvar.Messages.get; | |
| 43 | import static javafx.scene.control.Alert.AlertType.CONFIRMATION; | |
| 44 | import static javafx.scene.control.Alert.AlertType.ERROR; | |
| 45 | ||
| 46 | /** | |
| 47 | * Provides the ability to notify the user of problems. | |
| 48 | * | |
| 49 | * @author White Magic Software, Ltd. | |
| 50 | */ | |
| 51 | public final class DefaultNotifier extends Observable implements Notifier { | |
| 52 | ||
| 53 | private final static String OK = get( STATUS_BAR_OK, "OK" ); | |
| 54 | ||
| 55 | /** | |
| 56 | * Notifies all observer instances of the given message. | |
| 57 | * | |
| 58 | * @param message The text to display to the user. | |
| 59 | */ | |
| 60 | @Override | |
| 61 | public void notify( final String message ) { | |
| 62 | if( message != null && !message.isBlank() ) { | |
| 63 | setChanged(); | |
| 64 | notifyObservers( message ); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public void clear() { | |
| 70 | notify( OK ); | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Contains all the information that the user needs to know about a problem. | |
| 75 | * | |
| 76 | * @param title The context for the message. | |
| 77 | * @param message The message content (formatted with the given args). | |
| 78 | * @param args Parameters for the message content. | |
| 79 | * @return A notification instance, never null. | |
| 80 | */ | |
| 81 | @Override | |
| 82 | public Notification createNotification( | |
| 83 | final String title, | |
| 84 | final String message, | |
| 85 | final Object... args ) { | |
| 86 | return new DefaultNotification( title, message, args ); | |
| 87 | } | |
| 88 | ||
| 89 | private Alert createAlertDialog( | |
| 90 | final Window parent, | |
| 91 | final AlertType alertType, | |
| 92 | final Notification message ) { | |
| 93 | ||
| 94 | final Alert alert = new Alert( alertType ); | |
| 95 | ||
| 96 | alert.setDialogPane( new ButtonOrderPane() ); | |
| 97 | alert.setTitle( message.getTitle() ); | |
| 98 | alert.setHeaderText( null ); | |
| 99 | alert.setContentText( message.getContent() ); | |
| 100 | alert.initOwner( parent ); | |
| 101 | ||
| 102 | return alert; | |
| 103 | } | |
| 104 | ||
| 105 | @Override | |
| 106 | public Alert createConfirmation( final Window parent, | |
| 107 | final Notification message ) { | |
| 108 | final Alert alert = createAlertDialog( parent, CONFIRMATION, message ); | |
| 109 | ||
| 110 | alert.getButtonTypes().setAll( YES, NO, CANCEL ); | |
| 111 | ||
| 112 | return alert; | |
| 113 | } | |
| 114 | ||
| 115 | @Override | |
| 116 | public Alert createError( final Window parent, final Notification message ) { | |
| 117 | return createAlertDialog( parent, ERROR, message ); | |
| 118 | } | |
| 119 | ||
| 120 | @Override | |
| 121 | public File getLogPath() { | |
| 122 | return Paths.get( | |
| 123 | System.getProperty( "java.io.tmpdir" ), APP_TITLE + ".log" ).toFile(); | |
| 124 | } | |
| 125 | } | |
| 1 | 126 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.service.impl; | |
| 28 | ||
| 29 | import com.scrivenvar.preferences.UserPreferences; | |
| 30 | import com.scrivenvar.service.Options; | |
| 31 | ||
| 32 | import java.util.prefs.BackingStoreException; | |
| 33 | import java.util.prefs.Preferences; | |
| 34 | ||
| 35 | import static com.scrivenvar.Constants.PREFS_ROOT; | |
| 36 | import static com.scrivenvar.Constants.PREFS_STATE; | |
| 37 | import static java.util.prefs.Preferences.userRoot; | |
| 38 | ||
| 39 | /** | |
| 40 | * Persistent options user can change at runtime. | |
| 41 | * | |
| 42 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class DefaultOptions implements Options { | |
| 45 | private final UserPreferences mPreferences = new UserPreferences(); | |
| 46 | ||
| 47 | public DefaultOptions() { | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * This will throw IllegalArgumentException if the value exceeds the maximum | |
| 52 | * preferences value length. | |
| 53 | * | |
| 54 | * @param key The name of the key to associate with the value. | |
| 55 | * @param value The value to persist. | |
| 56 | * @throws BackingStoreException New value not persisted. | |
| 57 | */ | |
| 58 | @Override | |
| 59 | public void put( final String key, final String value ) | |
| 60 | throws BackingStoreException { | |
| 61 | getState().put( key, value ); | |
| 62 | getState().flush(); | |
| 63 | } | |
| 64 | ||
| 65 | @Override | |
| 66 | public String get( final String key, final String value ) { | |
| 67 | return getState().get( key, value ); | |
| 68 | } | |
| 69 | ||
| 70 | @Override | |
| 71 | public String get( final String key ) { | |
| 72 | return get( key, "" ); | |
| 73 | } | |
| 74 | ||
| 75 | private Preferences getRootPreferences() { | |
| 76 | return userRoot().node( PREFS_ROOT ); | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public Preferences getState() { | |
| 81 | return getRootPreferences().node( PREFS_STATE ); | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public UserPreferences getUserPreferences() { | |
| 86 | return mPreferences; | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Settings; | |
| 31 | import org.apache.commons.configuration2.PropertiesConfiguration; | |
| 32 | import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; | |
| 33 | import org.apache.commons.configuration2.convert.ListDelimiterHandler; | |
| 34 | import org.apache.commons.configuration2.ex.ConfigurationException; | |
| 35 | ||
| 36 | import java.io.IOException; | |
| 37 | import java.io.InputStreamReader; | |
| 38 | import java.io.Reader; | |
| 39 | import java.net.URL; | |
| 40 | import java.nio.charset.Charset; | |
| 41 | import java.util.Iterator; | |
| 42 | import java.util.List; | |
| 43 | ||
| 44 | import static com.scrivenvar.Constants.SETTINGS_NAME; | |
| 45 | ||
| 46 | /** | |
| 47 | * Responsible for loading settings that help avoid hard-coded assumptions. | |
| 48 | * | |
| 49 | * @author White Magic Software, Ltd. | |
| 50 | */ | |
| 51 | public class DefaultSettings implements Settings { | |
| 52 | ||
| 53 | private static final char VALUE_SEPARATOR = ','; | |
| 54 | ||
| 55 | private PropertiesConfiguration properties; | |
| 56 | ||
| 57 | public DefaultSettings() throws ConfigurationException { | |
| 58 | setProperties( createProperties() ); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Returns the value of a string property. | |
| 63 | * | |
| 64 | * @param property The property key. | |
| 65 | * @param defaultValue The value to return if no property key has been set. | |
| 66 | * @return The property key value, or defaultValue when no key found. | |
| 67 | */ | |
| 68 | @Override | |
| 69 | public String getSetting( final String property, final String defaultValue ) { | |
| 70 | return getSettings().getString( property, defaultValue ); | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Returns the value of a string property. | |
| 75 | * | |
| 76 | * @param property The property key. | |
| 77 | * @param defaultValue The value to return if no property key has been set. | |
| 78 | * @return The property key value, or defaultValue when no key found. | |
| 79 | */ | |
| 80 | @Override | |
| 81 | public int getSetting( final String property, final int defaultValue ) { | |
| 82 | return getSettings().getInt( property, defaultValue ); | |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Changes key's value. This will clear the old value before setting the new | |
| 87 | * value so that the old value is erased, not changed into a list. | |
| 88 | * | |
| 89 | * @param key The property key name to obtain its value. | |
| 90 | * @param value The new value to set. | |
| 91 | */ | |
| 92 | @Override | |
| 93 | public void putSetting( final String key, final String value ) { | |
| 94 | getSettings().clearProperty( key ); | |
| 95 | getSettings().addProperty( key, value ); | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Convert the generic list of property objects into strings. | |
| 100 | * | |
| 101 | * @param property The property value to coerce. | |
| 102 | * @param defaults The defaults values to use should the property be unset. | |
| 103 | * @return The list of properties coerced from objects to strings. | |
| 104 | */ | |
| 105 | @Override | |
| 106 | public List<String> getStringSettingList( | |
| 107 | final String property, final List<String> defaults ) { | |
| 108 | return getSettings().getList( String.class, property, defaults ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Convert a list of property objects into strings, with no default value. | |
| 113 | * | |
| 114 | * @param property The property value to coerce. | |
| 115 | * @return The list of properties coerced from objects to strings. | |
| 116 | */ | |
| 117 | @Override | |
| 118 | public List<String> getStringSettingList( final String property ) { | |
| 119 | return getStringSettingList( property, null ); | |
| 120 | } | |
| 121 | ||
| 122 | /** | |
| 123 | * Returns a list of property names that begin with the given prefix. | |
| 124 | * | |
| 125 | * @param prefix The prefix to compare against each property name. | |
| 126 | * @return The list of property names that have the given prefix. | |
| 127 | */ | |
| 128 | @Override | |
| 129 | public Iterator<String> getKeys( final String prefix ) { | |
| 130 | return getSettings().getKeys( prefix ); | |
| 131 | } | |
| 132 | ||
| 133 | private PropertiesConfiguration createProperties() | |
| 134 | throws ConfigurationException { | |
| 135 | ||
| 136 | final URL url = getPropertySource(); | |
| 137 | final PropertiesConfiguration configuration = new PropertiesConfiguration(); | |
| 138 | ||
| 139 | if( url != null ) { | |
| 140 | try( final Reader r = new InputStreamReader( url.openStream(), | |
| 141 | getDefaultEncoding() ) ) { | |
| 142 | configuration.setListDelimiterHandler( createListDelimiterHandler() ); | |
| 143 | configuration.read( r ); | |
| 144 | ||
| 145 | } catch( final IOException ex ) { | |
| 146 | throw new RuntimeException( new ConfigurationException( ex ) ); | |
| 147 | } | |
| 148 | } | |
| 149 | ||
| 150 | return configuration; | |
| 151 | } | |
| 152 | ||
| 153 | protected Charset getDefaultEncoding() { | |
| 154 | return Charset.defaultCharset(); | |
| 155 | } | |
| 156 | ||
| 157 | protected ListDelimiterHandler createListDelimiterHandler() { | |
| 158 | return new DefaultListDelimiterHandler( VALUE_SEPARATOR ); | |
| 159 | } | |
| 160 | ||
| 161 | private URL getPropertySource() { | |
| 162 | return DefaultSettings.class.getResource( getSettingsFilename() ); | |
| 163 | } | |
| 164 | ||
| 165 | private String getSettingsFilename() { | |
| 166 | return SETTINGS_NAME; | |
| 167 | } | |
| 168 | ||
| 169 | private void setProperties( final PropertiesConfiguration configuration ) { | |
| 170 | this.properties = configuration; | |
| 171 | } | |
| 172 | ||
| 173 | private PropertiesConfiguration getSettings() { | |
| 174 | return this.properties; | |
| 175 | } | |
| 176 | } | |
| 1 | 177 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Snitch; | |
| 31 | ||
| 32 | import java.io.IOException; | |
| 33 | import java.nio.file.*; | |
| 34 | import java.util.Collections; | |
| 35 | import java.util.Map; | |
| 36 | import java.util.Observable; | |
| 37 | import java.util.Set; | |
| 38 | import java.util.concurrent.ConcurrentHashMap; | |
| 39 | ||
| 40 | import static com.scrivenvar.Constants.APP_WATCHDOG_TIMEOUT; | |
| 41 | import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | |
| 42 | ||
| 43 | /** | |
| 44 | * Listens for file changes. Other classes can register paths to be monitored | |
| 45 | * and listen for changes to those paths. | |
| 46 | * | |
| 47 | * @author White Magic Software, Ltd. | |
| 48 | */ | |
| 49 | public class DefaultSnitch extends Observable implements Snitch { | |
| 50 | ||
| 51 | /** | |
| 52 | * Service for listening to directories for modifications. | |
| 53 | */ | |
| 54 | private WatchService watchService; | |
| 55 | ||
| 56 | /** | |
| 57 | * Directories being monitored for changes. | |
| 58 | */ | |
| 59 | private Map<WatchKey, Path> keys; | |
| 60 | ||
| 61 | /** | |
| 62 | * Files that will kick off notification events if modified. | |
| 63 | */ | |
| 64 | private Set<Path> eavesdropped; | |
| 65 | ||
| 66 | /** | |
| 67 | * Set to true when running; set to false to stop listening. | |
| 68 | */ | |
| 69 | private volatile boolean listening; | |
| 70 | ||
| 71 | public DefaultSnitch() { | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void stop() { | |
| 76 | setListening( false ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Adds a listener to the list of files to watch for changes. If the file is | |
| 81 | * already in the monitored list, this will return immediately. | |
| 82 | * | |
| 83 | * @param file Path to a file to watch for changes. | |
| 84 | * @throws IOException The file could not be monitored. | |
| 85 | */ | |
| 86 | @Override | |
| 87 | public void listen( final Path file ) throws IOException { | |
| 88 | if( file != null && getEavesdropped().add( file ) ) { | |
| 89 | final Path dir = toDirectory( file ); | |
| 90 | final WatchKey key = dir.register( getWatchService(), ENTRY_MODIFY ); | |
| 91 | ||
| 92 | getWatchMap().put( key, dir ); | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Returns the given path to a file (or directory) as a directory. If the | |
| 98 | * given path is already a directory, it is returned. Otherwise, this returns | |
| 99 | * the directory that contains the file. This will fail if the file is stored | |
| 100 | * in the root folder. | |
| 101 | * | |
| 102 | * @param path The file to return as a directory, which should always be the | |
| 103 | * case. | |
| 104 | * @return The given path as a directory, if a file, otherwise the path | |
| 105 | * itself. | |
| 106 | */ | |
| 107 | private Path toDirectory( final Path path ) { | |
| 108 | return Files.isDirectory( path ) | |
| 109 | ? path | |
| 110 | : path.toFile().getParentFile().toPath(); | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Stop listening to the given file for change events. This fails silently. | |
| 115 | * | |
| 116 | * @param file The file to no longer monitor for changes. | |
| 117 | */ | |
| 118 | @Override | |
| 119 | public void ignore( final Path file ) { | |
| 120 | if( file != null ) { | |
| 121 | final Path directory = toDirectory( file ); | |
| 122 | ||
| 123 | // Remove all occurrences (there should be only one). | |
| 124 | getWatchMap().values().removeAll( Collections.singleton( directory ) ); | |
| 125 | ||
| 126 | // Remove all occurrences (there can be only one). | |
| 127 | getEavesdropped().remove( file ); | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Loops until stop is called, or the application is terminated. | |
| 133 | */ | |
| 134 | @Override | |
| 135 | @SuppressWarnings("BusyWait") | |
| 136 | public void run() { | |
| 137 | setListening( true ); | |
| 138 | ||
| 139 | while( isListening() ) { | |
| 140 | try { | |
| 141 | final WatchKey key = getWatchService().take(); | |
| 142 | final Path path = get( key ); | |
| 143 | ||
| 144 | // Prevent receiving two separate ENTRY_MODIFY events: file modified | |
| 145 | // and timestamp updated. Instead, receive one ENTRY_MODIFY event | |
| 146 | // with two counts. | |
| 147 | Thread.sleep( APP_WATCHDOG_TIMEOUT ); | |
| 148 | ||
| 149 | for( final WatchEvent<?> event : key.pollEvents() ) { | |
| 150 | final Path changed = path.resolve( (Path) event.context() ); | |
| 151 | ||
| 152 | if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) { | |
| 153 | setChanged(); | |
| 154 | notifyObservers( changed ); | |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | if( !key.reset() ) { | |
| 159 | ignore( path ); | |
| 160 | } | |
| 161 | } catch( final IOException | InterruptedException ex ) { | |
| 162 | // Stop eavesdropping. | |
| 163 | setListening( false ); | |
| 164 | } | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Returns true if the list of files being listened to for changes contains | |
| 170 | * the given file. | |
| 171 | * | |
| 172 | * @param file Path to a system file. | |
| 173 | * @return true The given file is being monitored for changes. | |
| 174 | */ | |
| 175 | private boolean isListening( final Path file ) { | |
| 176 | return getEavesdropped().contains( file ); | |
| 177 | } | |
| 178 | ||
| 179 | /** | |
| 180 | * Returns a path for a given watch key. | |
| 181 | * | |
| 182 | * @param key The key to lookup its corresponding path. | |
| 183 | * @return The path for the given key. | |
| 184 | */ | |
| 185 | private Path get( final WatchKey key ) { | |
| 186 | return getWatchMap().get( key ); | |
| 187 | } | |
| 188 | ||
| 189 | private synchronized Map<WatchKey, Path> getWatchMap() { | |
| 190 | if( this.keys == null ) { | |
| 191 | this.keys = createWatchKeys(); | |
| 192 | } | |
| 193 | ||
| 194 | return this.keys; | |
| 195 | } | |
| 196 | ||
| 197 | protected Map<WatchKey, Path> createWatchKeys() { | |
| 198 | return new ConcurrentHashMap<>(); | |
| 199 | } | |
| 200 | ||
| 201 | /** | |
| 202 | * Returns a list of files that, when changed, will kick off a notification. | |
| 203 | * | |
| 204 | * @return A non-null, possibly empty, list of files. | |
| 205 | */ | |
| 206 | private synchronized Set<Path> getEavesdropped() { | |
| 207 | if( this.eavesdropped == null ) { | |
| 208 | this.eavesdropped = createEavesdropped(); | |
| 209 | } | |
| 210 | ||
| 211 | return this.eavesdropped; | |
| 212 | } | |
| 213 | ||
| 214 | protected Set<Path> createEavesdropped() { | |
| 215 | return ConcurrentHashMap.newKeySet(); | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * The existing watch service, or a new instance if null. | |
| 220 | * | |
| 221 | * @return A valid WatchService instance, never null. | |
| 222 | * @throws IOException Could not create a new watch service. | |
| 223 | */ | |
| 224 | private synchronized WatchService getWatchService() throws IOException { | |
| 225 | if( this.watchService == null ) { | |
| 226 | this.watchService = createWatchService(); | |
| 227 | } | |
| 228 | ||
| 229 | return this.watchService; | |
| 230 | } | |
| 231 | ||
| 232 | protected WatchService createWatchService() throws IOException { | |
| 233 | final FileSystem fileSystem = FileSystems.getDefault(); | |
| 234 | return fileSystem.newWatchService(); | |
| 235 | } | |
| 236 | ||
| 237 | /** | |
| 238 | * Answers whether the loop should continue executing. | |
| 239 | * | |
| 240 | * @return true The internal listening loop should continue listening for file | |
| 241 | * modification events. | |
| 242 | */ | |
| 243 | protected boolean isListening() { | |
| 244 | return this.listening; | |
| 245 | } | |
| 246 | ||
| 247 | /** | |
| 248 | * Requests the snitch to stop eavesdropping on file changes. | |
| 249 | * | |
| 250 | * @param listening Use true to indicate the service should stop running. | |
| 251 | */ | |
| 252 | private void setListening( final boolean listening ) { | |
| 253 | this.listening = listening; | |
| 254 | } | |
| 255 | } | |
| 1 | 256 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import de.jensd.fx.glyphs.GlyphIcons; | |
| 30 | import javafx.beans.value.ObservableBooleanValue; | |
| 31 | import javafx.event.ActionEvent; | |
| 32 | import javafx.event.EventHandler; | |
| 33 | import javafx.scene.input.KeyCombination; | |
| 34 | ||
| 35 | /** | |
| 36 | * Simple action class | |
| 37 | * | |
| 38 | * @author Karl Tauber | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class Action { | |
| 42 | public final String text; | |
| 43 | public final KeyCombination accelerator; | |
| 44 | public final GlyphIcons icon; | |
| 45 | public final EventHandler<ActionEvent> action; | |
| 46 | public final ObservableBooleanValue disable; | |
| 47 | ||
| 48 | public Action( | |
| 49 | final String text, | |
| 50 | final String accelerator, | |
| 51 | final GlyphIcons icon, | |
| 52 | final EventHandler<ActionEvent> action, | |
| 53 | final ObservableBooleanValue disable ) { | |
| 54 | ||
| 55 | this.text = text; | |
| 56 | this.accelerator = accelerator == null ? | |
| 57 | null : KeyCombination.valueOf( accelerator ); | |
| 58 | this.icon = icon; | |
| 59 | this.action = action; | |
| 60 | this.disable = disable; | |
| 61 | } | |
| 62 | } | |
| 1 | 63 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import com.scrivenvar.Messages; | |
| 31 | import de.jensd.fx.glyphs.GlyphIcons; | |
| 32 | import javafx.beans.value.ObservableBooleanValue; | |
| 33 | import javafx.event.ActionEvent; | |
| 34 | import javafx.event.EventHandler; | |
| 35 | ||
| 36 | /** | |
| 37 | * Provides a fluent interface around constructing actions so that duplication | |
| 38 | * can be avoided. | |
| 39 | */ | |
| 40 | public class ActionBuilder { | |
| 41 | private String mText; | |
| 42 | private String mAccelerator; | |
| 43 | private GlyphIcons mIcon; | |
| 44 | private EventHandler<ActionEvent> mAction; | |
| 45 | private ObservableBooleanValue mDisable; | |
| 46 | ||
| 47 | /** | |
| 48 | * Sets the action text based on a resource bundle key. | |
| 49 | * | |
| 50 | * @param key The key to look up in the {@link Messages}. | |
| 51 | * @return The corresponding value, or the key name if none found. | |
| 52 | */ | |
| 53 | public ActionBuilder setText( final String key ) { | |
| 54 | mText = Messages.get( key, key ); | |
| 55 | return this; | |
| 56 | } | |
| 57 | ||
| 58 | public ActionBuilder setAccelerator( final String accelerator ) { | |
| 59 | mAccelerator = accelerator; | |
| 60 | return this; | |
| 61 | } | |
| 62 | ||
| 63 | public ActionBuilder setIcon( final GlyphIcons icon ) { | |
| 64 | mIcon = icon; | |
| 65 | return this; | |
| 66 | } | |
| 67 | ||
| 68 | public ActionBuilder setAction( final EventHandler<ActionEvent> action ) { | |
| 69 | mAction = action; | |
| 70 | return this; | |
| 71 | } | |
| 72 | ||
| 73 | public ActionBuilder setDisable( final ObservableBooleanValue disable ) { | |
| 74 | mDisable = disable; | |
| 75 | return this; | |
| 76 | } | |
| 77 | ||
| 78 | public Action build() { | |
| 79 | return new Action( mText, mAccelerator, mIcon, mAction, mDisable ); | |
| 80 | } | |
| 81 | } | |
| 1 | 82 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 30 | import javafx.scene.Node; | |
| 31 | import javafx.scene.control.Button; | |
| 32 | import javafx.scene.control.Menu; | |
| 33 | import javafx.scene.control.MenuItem; | |
| 34 | import javafx.scene.control.Separator; | |
| 35 | import javafx.scene.control.SeparatorMenuItem; | |
| 36 | import javafx.scene.control.ToolBar; | |
| 37 | import javafx.scene.control.Tooltip; | |
| 38 | ||
| 39 | /** | |
| 40 | * Action utilities | |
| 41 | * | |
| 42 | * @author Karl Tauber | |
| 43 | * @author White Magic Software, Ltd. | |
| 44 | */ | |
| 45 | public class ActionUtils { | |
| 46 | ||
| 47 | public static Menu createMenu( final String text, final Action... actions ) { | |
| 48 | return new Menu( text, null, createMenuItems( actions ) ); | |
| 49 | } | |
| 50 | ||
| 51 | public static MenuItem[] createMenuItems( final Action... actions ) { | |
| 52 | final MenuItem[] menuItems = new MenuItem[ actions.length ]; | |
| 53 | ||
| 54 | for( int i = 0; i < actions.length; i++ ) { | |
| 55 | menuItems[ i ] = (actions[ i ] != null) | |
| 56 | ? createMenuItem( actions[ i ] ) | |
| 57 | : new SeparatorMenuItem(); | |
| 58 | } | |
| 59 | ||
| 60 | return menuItems; | |
| 61 | } | |
| 62 | ||
| 63 | public static MenuItem createMenuItem( final Action action ) { | |
| 64 | final MenuItem menuItem = new MenuItem( action.text ); | |
| 65 | ||
| 66 | if( action.accelerator != null ) { | |
| 67 | menuItem.setAccelerator( action.accelerator ); | |
| 68 | } | |
| 69 | ||
| 70 | if( action.icon != null ) { | |
| 71 | menuItem.setGraphic( | |
| 72 | FontAwesomeIconFactory.get().createIcon( action.icon ) ); | |
| 73 | } | |
| 74 | ||
| 75 | menuItem.setOnAction( action.action ); | |
| 76 | ||
| 77 | if( action.disable != null ) { | |
| 78 | menuItem.disableProperty().bind( action.disable ); | |
| 79 | } | |
| 80 | ||
| 81 | menuItem.setMnemonicParsing( true ); | |
| 82 | ||
| 83 | return menuItem; | |
| 84 | } | |
| 85 | ||
| 86 | public static ToolBar createToolBar( final Action... actions ) { | |
| 87 | return new ToolBar( createToolBarButtons( actions ) ); | |
| 88 | } | |
| 89 | ||
| 90 | public static Node[] createToolBarButtons( final Action... actions ) { | |
| 91 | Node[] buttons = new Node[ actions.length ]; | |
| 92 | for( int i = 0; i < actions.length; i++ ) { | |
| 93 | buttons[ i ] = (actions[ i ] != null) | |
| 94 | ? createToolBarButton( actions[ i ] ) | |
| 95 | : new Separator(); | |
| 96 | } | |
| 97 | return buttons; | |
| 98 | } | |
| 99 | ||
| 100 | public static Button createToolBarButton( final Action action ) { | |
| 101 | final Button button = new Button(); | |
| 102 | button.setGraphic( | |
| 103 | FontAwesomeIconFactory | |
| 104 | .get() | |
| 105 | .createIcon( action.icon, "1.2em" ) ); | |
| 106 | ||
| 107 | String tooltip = action.text; | |
| 108 | ||
| 109 | if( tooltip.endsWith( "..." ) ) { | |
| 110 | tooltip = tooltip.substring( 0, tooltip.length() - 3 ); | |
| 111 | } | |
| 112 | ||
| 113 | if( action.accelerator != null ) { | |
| 114 | tooltip += " (" + action.accelerator.getDisplayText() + ')'; | |
| 115 | } | |
| 116 | ||
| 117 | button.setTooltip( new Tooltip( tooltip ) ); | |
| 118 | button.setFocusTraversable( false ); | |
| 119 | button.setOnAction( action.action ); | |
| 120 | ||
| 121 | if( action.disable != null ) { | |
| 122 | button.disableProperty().bind( action.disable ); | |
| 123 | } | |
| 124 | ||
| 125 | return button; | |
| 126 | } | |
| 127 | } | |
| 1 | 128 |
| 1 | /* | |
| 2 | * Copyright 2016 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import java.util.List; | |
| 31 | ||
| 32 | /** | |
| 33 | * Convenience class that provides a clearer API for obtaining list elements. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public final class Lists { | |
| 38 | ||
| 39 | private Lists() { | |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * Returns the first item in the given list, or null if not found. | |
| 44 | * | |
| 45 | * @param <T> The generic list type. | |
| 46 | * @param list The list that may have a first item. | |
| 47 | * | |
| 48 | * @return null if the list is null or there is no first item. | |
| 49 | */ | |
| 50 | public static <T> T getFirst( final List<T> list ) { | |
| 51 | return getFirst( list, null ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Returns the last item in the given list, or null if not found. | |
| 56 | * | |
| 57 | * @param <T> The generic list type. | |
| 58 | * @param list The list that may have a last item. | |
| 59 | * | |
| 60 | * @return null if the list is null or there is no last item. | |
| 61 | */ | |
| 62 | public static <T> T getLast( final List<T> list ) { | |
| 63 | return getLast( list, null ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Returns the first item in the given list, or t if not found. | |
| 68 | * | |
| 69 | * @param <T> The generic list type. | |
| 70 | * @param list The list that may have a first item. | |
| 71 | * @param t The default return value. | |
| 72 | * | |
| 73 | * @return null if the list is null or there is no first item. | |
| 74 | */ | |
| 75 | public static <T> T getFirst( final List<T> list, final T t ) { | |
| 76 | return isEmpty( list ) ? t : list.get( 0 ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Returns the last item in the given list, or t if not found. | |
| 81 | * | |
| 82 | * @param <T> The generic list type. | |
| 83 | * @param list The list that may have a last item. | |
| 84 | * @param t The default return value. | |
| 85 | * | |
| 86 | * @return null if the list is null or there is no last item. | |
| 87 | */ | |
| 88 | public static <T> T getLast( final List<T> list, final T t ) { | |
| 89 | return isEmpty( list ) ? t : list.get( list.size() - 1 ); | |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Returns true if the given list is null or empty. | |
| 94 | * | |
| 95 | * @param <T> The generic list type. | |
| 96 | * @param list The list that has a last item. | |
| 97 | * | |
| 98 | * @return true The list is empty. | |
| 99 | */ | |
| 100 | public static <T> boolean isEmpty( final List<T> list ) { | |
| 101 | return list == null || list.isEmpty(); | |
| 102 | } | |
| 103 | } | |
| 1 | 104 |
| 1 | package com.scrivenvar.util; | |
| 2 | ||
| 3 | import java.io.File; | |
| 4 | import java.net.URI; | |
| 5 | import java.net.URL; | |
| 6 | ||
| 7 | import static com.scrivenvar.Constants.DEFINITION_PROTOCOL_UNKNOWN; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for determining the protocol of a resource. | |
| 11 | */ | |
| 12 | public class ProtocolResolver { | |
| 13 | /** | |
| 14 | * Returns the protocol for a given URI or filename. | |
| 15 | * | |
| 16 | * @param resource Determine the protocol for this URI or filename. | |
| 17 | * @return The protocol for the given source. | |
| 18 | */ | |
| 19 | public static String getProtocol( final String resource ) { | |
| 20 | String protocol; | |
| 21 | ||
| 22 | try { | |
| 23 | final URI uri = new URI( resource ); | |
| 24 | ||
| 25 | if( uri.isAbsolute() ) { | |
| 26 | protocol = uri.getScheme(); | |
| 27 | } | |
| 28 | else { | |
| 29 | final URL url = new URL( resource ); | |
| 30 | protocol = url.getProtocol(); | |
| 31 | } | |
| 32 | } catch( final Exception e ) { | |
| 33 | // Could be HTTP, HTTPS? | |
| 34 | if( resource.startsWith( "//" ) ) { | |
| 35 | throw new IllegalArgumentException( "Relative context: " + resource ); | |
| 36 | } | |
| 37 | else { | |
| 38 | final File file = new File( resource ); | |
| 39 | protocol = getProtocol( file ); | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | return protocol; | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Returns the protocol for a given file. | |
| 48 | * | |
| 49 | * @param file Determine the protocol for this file. | |
| 50 | * @return The protocol for the given file. | |
| 51 | */ | |
| 52 | public static String getProtocol( final File file ) { | |
| 53 | String result; | |
| 54 | ||
| 55 | try { | |
| 56 | result = file.toURI().toURL().getProtocol(); | |
| 57 | } catch( final Exception e ) { | |
| 58 | result = DEFINITION_PROTOCOL_UNKNOWN; | |
| 59 | } | |
| 60 | ||
| 61 | return result; | |
| 62 | } | |
| 63 | } | |
| 1 | 64 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.prefs.Preferences; | |
| 30 | ||
| 31 | import javafx.application.Platform; | |
| 32 | import javafx.scene.shape.Rectangle; | |
| 33 | import javafx.stage.Stage; | |
| 34 | import javafx.stage.WindowEvent; | |
| 35 | ||
| 36 | /** | |
| 37 | * Saves and restores Stage state (window bounds, maximized, fullScreen). | |
| 38 | * | |
| 39 | * @author Karl Tauber | |
| 40 | */ | |
| 41 | public class StageState { | |
| 42 | ||
| 43 | public static final String K_PANE_SPLIT_DEFINITION = "pane.split.definition"; | |
| 44 | public static final String K_PANE_SPLIT_EDITOR = "pane.split.editor"; | |
| 45 | public static final String K_PANE_SPLIT_PREVIEW = "pane.split.preview"; | |
| 46 | ||
| 47 | private final Stage mStage; | |
| 48 | private final Preferences mState; | |
| 49 | ||
| 50 | private Rectangle normalBounds; | |
| 51 | private boolean runLaterPending; | |
| 52 | ||
| 53 | public StageState( final Stage stage, final Preferences state ) { | |
| 54 | mStage = stage; | |
| 55 | mState = state; | |
| 56 | ||
| 57 | restore(); | |
| 58 | ||
| 59 | stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() ); | |
| 60 | ||
| 61 | stage.xProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 62 | stage.yProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 63 | stage.widthProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 64 | stage.heightProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 65 | } | |
| 66 | ||
| 67 | private void save() { | |
| 68 | final Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds; | |
| 69 | ||
| 70 | if( bounds != null ) { | |
| 71 | mState.putDouble( "windowX", bounds.getX() ); | |
| 72 | mState.putDouble( "windowY", bounds.getY() ); | |
| 73 | mState.putDouble( "windowWidth", bounds.getWidth() ); | |
| 74 | mState.putDouble( "windowHeight", bounds.getHeight() ); | |
| 75 | } | |
| 76 | ||
| 77 | mState.putBoolean( "windowMaximized", mStage.isMaximized() ); | |
| 78 | mState.putBoolean( "windowFullScreen", mStage.isFullScreen() ); | |
| 79 | } | |
| 80 | ||
| 81 | private void restore() { | |
| 82 | final double x = mState.getDouble( "windowX", Double.NaN ); | |
| 83 | final double y = mState.getDouble( "windowY", Double.NaN ); | |
| 84 | final double w = mState.getDouble( "windowWidth", Double.NaN ); | |
| 85 | final double h = mState.getDouble( "windowHeight", Double.NaN ); | |
| 86 | final boolean maximized = mState.getBoolean( "windowMaximized", false ); | |
| 87 | final boolean fullScreen = mState.getBoolean( "windowFullScreen", false ); | |
| 88 | ||
| 89 | if( !Double.isNaN( x ) && !Double.isNaN( y ) ) { | |
| 90 | mStage.setX( x ); | |
| 91 | mStage.setY( y ); | |
| 92 | } // else: default behavior is center on screen | |
| 93 | ||
| 94 | if( !Double.isNaN( w ) && !Double.isNaN( h ) ) { | |
| 95 | mStage.setWidth( w ); | |
| 96 | mStage.setHeight( h ); | |
| 97 | } // else: default behavior is use scene size | |
| 98 | ||
| 99 | if( fullScreen != mStage.isFullScreen() ) { | |
| 100 | mStage.setFullScreen( fullScreen ); | |
| 101 | } | |
| 102 | ||
| 103 | if( maximized != mStage.isMaximized() ) { | |
| 104 | mStage.setMaximized( maximized ); | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Remembers the window bounds when the window is not iconified, maximized or | |
| 110 | * in fullScreen. | |
| 111 | */ | |
| 112 | private void boundsChanged() { | |
| 113 | // avoid too many (and useless) runLater() invocations | |
| 114 | if( runLaterPending ) { | |
| 115 | return; | |
| 116 | } | |
| 117 | ||
| 118 | runLaterPending = true; | |
| 119 | ||
| 120 | // must use runLater() to ensure that change of all properties | |
| 121 | // (x, y, width, height, iconified, maximized and fullScreen) | |
| 122 | // has finished | |
| 123 | Platform.runLater( () -> { | |
| 124 | runLaterPending = false; | |
| 125 | ||
| 126 | if( isNormalState() ) { | |
| 127 | normalBounds = getStageBounds(); | |
| 128 | } | |
| 129 | } ); | |
| 130 | } | |
| 131 | ||
| 132 | private boolean isNormalState() { | |
| 133 | return !mStage.isIconified() && | |
| 134 | !mStage.isMaximized() && | |
| 135 | !mStage.isFullScreen(); | |
| 136 | } | |
| 137 | ||
| 138 | private Rectangle getStageBounds() { | |
| 139 | return new Rectangle( | |
| 140 | mStage.getX(), | |
| 141 | mStage.getY(), | |
| 142 | mStage.getWidth(), | |
| 143 | mStage.getHeight() | |
| 144 | ); | |
| 145 | } | |
| 146 | } | |
| 1 | 147 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.ArrayList; | |
| 30 | import java.util.prefs.Preferences; | |
| 31 | ||
| 32 | /** | |
| 33 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class Utils { | |
| 36 | ||
| 37 | public static String ltrim( final String s ) { | |
| 38 | int i = 0; | |
| 39 | ||
| 40 | while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 41 | i++; | |
| 42 | } | |
| 43 | ||
| 44 | return s.substring( i ); | |
| 45 | } | |
| 46 | ||
| 47 | public static String rtrim( final String s ) { | |
| 48 | int i = s.length() - 1; | |
| 49 | ||
| 50 | while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 51 | i--; | |
| 52 | } | |
| 53 | ||
| 54 | return s.substring( 0, i + 1 ); | |
| 55 | } | |
| 56 | ||
| 57 | public static String[] getPrefsStrings( final Preferences prefs, | |
| 58 | String key ) { | |
| 59 | final ArrayList<String> arr = new ArrayList<>( 256 ); | |
| 60 | ||
| 61 | for( int i = 0; i < 10000; i++ ) { | |
| 62 | final String s = prefs.get( key + (i + 1), null ); | |
| 63 | ||
| 64 | if( s == null ) { | |
| 65 | break; | |
| 66 | } | |
| 67 | ||
| 68 | arr.add( s ); | |
| 69 | } | |
| 70 | ||
| 71 | return arr.toArray( new String[ 0 ] ); | |
| 72 | } | |
| 73 | ||
| 74 | public static void putPrefsStrings( Preferences prefs, String key, | |
| 75 | String[] strings ) { | |
| 76 | for( int i = 0; i < strings.length; i++ ) { | |
| 77 | prefs.put( key + (i + 1), strings[ i ] ); | |
| 78 | } | |
| 79 | ||
| 80 | for( int i = strings.length; prefs.get( key + (i + 1), | |
| 81 | null ) != null; i++ ) { | |
| 82 | prefs.remove( key + (i + 1) ); | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 1 | 86 |
| 1 | # R Scripts | |
| 2 | ||
| 3 | These R scripts illustrate how R can be used within an application to perform calculations using variables. Authors are free to write their own scripts, of course. These scripts serve as an example of how to automate certain tasks while writing. | |
| 4 | ||
| 5 | ## Configuration | |
| 6 | ||
| 7 | Configure the editor to use the R scripts as follows: | |
| 8 | ||
| 9 | 1. Copy the R scripts into same directory as your Markdown files. | |
| 10 | 1. Start the editor. | |
| 11 | 1. Click **Tools → R Script**. | |
| 12 | 1. Copy and paste the following: | |
| 13 | ||
| 14 | assign( 'anchor', as.Date( '$date.anchor$', format='%Y-%m-%d' ), envir = .GlobalEnv ); | |
| 15 | setwd( '$application.r.working.directory$' ); | |
| 16 | source( 'pluralize.R' ); | |
| 17 | source( 'csv.R' ); | |
| 18 | source( 'conversion.R' ); | |
| 19 | ||
| 20 | 1. Click **File → New** to create a new file. | |
| 21 | 1. Click **File → Save As** to set a filename. | |
| 22 | 1. Set **Name** to: `variables.yaml` | |
| 23 | 1. Click **OK**. | |
| 24 | 1. Paste the following definitions: | |
| 25 | ||
| 26 | date: | |
| 27 | anchor: 2017-01-01 | |
| 28 | editor: | |
| 29 | examples: | |
| 30 | season: 2017-09-02 | |
| 31 | math: | |
| 32 | x: 1 | |
| 33 | y: $editor.examples.math.x$ + 1 | |
| 34 | z: $editor.examples.math.y$ + 1 | |
| 35 | name: | |
| 36 | given: Josephene | |
| 37 | ||
| 38 | 1. Save and close the file. | |
| 39 | 1. Click **File → Open** | |
| 40 | 1. Change **Markdown Files** to **Definition Files**. | |
| 41 | 1. Select `variables.yaml`. | |
| 42 | 1. Click **Open**. | |
| 43 | ||
| 44 | R functionality is configured. | |
| 45 | ||
| 46 | ## Definitions | |
| 47 | ||
| 48 | The variables definitions within `variables.yaml` are available to R using the R syntax. An additional variable, `application.r.working.directory` is added to the list of variables. The value is set to the working directory of the file being edited. Hover the mouse cursor over the file tab in the editor to see the full path to the file. | |
| 49 | ||
| 50 | ## Examples | |
| 51 | ||
| 52 | This section demonstrates how to use the R functions when editing. Complete the following steps to begin: | |
| 53 | ||
| 54 | 1. Click **File → New** to create a new file. | |
| 55 | 1. Click **File → Save As** to set a filename. | |
| 56 | 1. Set **Name** to: `example.Rmd` | |
| 57 | 1. Click **OK**. | |
| 58 | ||
| 59 | The examples are ready for use within the editor. | |
| 60 | ||
| 61 | ### Arithmetic | |
| 62 | ||
| 63 | Type the following to perform a simple calculation: | |
| 64 | ||
| 65 | `r# 1+1` | |
| 66 | ||
| 67 | The preview pane shows `2.0`. | |
| 68 | ||
| 69 | ### Functions | |
| 70 | ||
| 71 | Call the [format](https://stat.ethz.ch/R-manual/R-devel/library/base/html/format.html) function to truncate unwanted decimal places as follows: | |
| 72 | ||
| 73 | `r# format(1+1,digits=1)` | |
| 74 | ||
| 75 | The preview pane shows `2`. | |
| 76 | ||
| 77 | ### Pluralize | |
| 78 | ||
| 79 | Many English words can be pluralized as follows: | |
| 80 | ||
| 81 | `r# pl('wolf',2)` | |
| 82 | ||
| 83 | The preview pane shows `wolves`. The `pluralize.R` file contains a partial implementation of Damian Conway's algorithmic approach to English pluralization. | |
| 84 | ||
| 85 | ### Chicago Manual of Style | |
| 86 | ||
| 87 | Apply the Chicago Manual of Style for words less than one-hundred as follows: | |
| 88 | ||
| 89 | `r# cms(1)` `r# cms(99)` `r# cms(101)` | |
| 90 | ||
| 91 | The preview pane shows numbers written out as `one` and `ninety-nine`, followed by the digits 101. | |
| 92 | ||
| 93 | ### Data Import | |
| 94 | ||
| 95 | Import and display information from a CSV file as follows: | |
| 96 | ||
| 97 | 1. Click **File → New** to create a new file. | |
| 98 | 1. Click **File → Save As** to rename the file. | |
| 99 | 1. Set the filename to: `data.csv` | |
| 100 | 1. Paste the following into `data.csv`: | |
| 101 | ||
| 102 | Animal,Quantity,Country | |
| 103 | Aardwolf,1,Africa | |
| 104 | Keel-billed toucan,1,Belize | |
| 105 | Beaver,2,Canada | |
| 106 | Mute swan,3,Denmark | |
| 107 | Lion,5,Ethiopia | |
| 108 | Brown bear,8,Finland | |
| 109 | Dolphin,13,Greece | |
| 110 | Turul,21,Hungary | |
| 111 | Gyrfalcon,34,Iceland | |
| 112 | Red-billed streamertail,55,Jamaica | |
| 113 | ||
| 114 | 1. Click the `example.Rmd` tab. | |
| 115 | 1. Type the following: | |
| 116 | ||
| 117 | `r# csv2md('data.csv',total=F)` | |
| 118 | ||
| 119 | 1. Type the following to calculate a total for all numeric columns: | |
| 120 | ||
| 121 | `r# csv2md('data.csv')` | |
| 122 | ||
| 123 | This imports the data from an external file and formats the information into a table, automatically. Update the data as follows: | |
| 124 | ||
| 125 | 1. Click the `data.csv` tab to edit the data. | |
| 126 | 1. Change the data by adding a new row. | |
| 127 | 1. Save the file. | |
| 128 | 1. Click the `example.Rmd` tab. | |
| 129 | ||
| 130 | The preview pane shows the revised contents. | |
| 131 | ||
| 132 | ### Elapsed Time | |
| 133 | ||
| 134 | The duration of a timeline, given in numbers of days, can be computed into English as follows: | |
| 135 | ||
| 136 | `r# elapsed(1,1)` | |
| 137 | ||
| 138 | The preview pane shows `same day`. Change the expression to: | |
| 139 | ||
| 140 | `r# elapsed(1,2)` | |
| 141 | ||
| 142 | The preview pane shows `one day`. Change the expression to: | |
| 143 | ||
| 144 | `r# elapsed(1,112358)` | |
| 145 | ||
| 146 | The preview pane shows `307 years, seven months, and sixteen days`, combined using the Chicago Manual of Style, the pluralization function, and a [serial comma](https://www.behance.net/gallery/19417363/The-Oxford-Comma). | |
| 147 | ||
| 148 | ### Variable Syntax | |
| 149 | ||
| 150 | The syntax for a variable changes when using an R Markdown file (denoted by the `.Rmd` filename extension), as opposed to a regular Markdown file (`.md`). Return to the example file and type the following: | |
| 151 | ||
| 152 | `r# v$date$anchor` | |
| 153 | ||
| 154 | The preview pane shows the date. | |
| 155 | ||
| 156 | ### Autocomplete | |
| 157 | ||
| 158 | Automatically insert a variable reference into the text as follows: | |
| 159 | ||
| 160 | 1. Type: `Jos` | |
| 161 | * Note the capital letter, matches are case sensitive. | |
| 162 | 1. Hold down the `Control` key. | |
| 163 | 1. Tap the `Spacebar` | |
| 164 | ||
| 165 | The editor shows: | |
| 166 | ||
| 167 | `r#x( v$editor$examples$name$given )` | |
| 168 | ||
| 169 | The preview pane shows: | |
| 170 | ||
| 171 | Josephine | |
| 172 | ||
| 173 | Here, the `x` function evaluates its parameter as an expression. This allows variables to include expressions in their definition. | |
| 174 | ||
| 175 | ### Variable Definition Expressions | |
| 176 | ||
| 177 | Definition file variables are have the ability to reference other definitions. Try the following: | |
| 178 | ||
| 179 | x = `r#x( v$editor$examples$math$x )`; | |
| 180 | y = `r#x( v$editor$examples$math$y )`; | |
| 181 | z = `r#x( v$editor$examples$math$z )` | |
| 182 | ||
| 183 | The preview pane shows: | |
| 184 | ||
| 185 | x = 1.0; y = 2.0; z = 3.0 | |
| 186 | ||
| 187 | ### Case | |
| 188 | ||
| 189 | Ensure words begin with a lowercase letter as follows: | |
| 190 | ||
| 191 | `r#lc( v$editor$examples$name$given )` | |
| 192 | ||
| 193 | The preview pane shows: | |
| 194 | ||
| 195 | josephine | |
| 196 | ||
| 197 | Similarly, ensure an uppercase letter as follows: | |
| 198 | ||
| 199 | `r#uc( 'hello, world!' )` | |
| 200 | ||
| 201 | The preview pane shows: | |
| 202 | ||
| 203 | Hello, world! | |
| 204 | ||
| 205 | ### Month | |
| 206 | ||
| 207 | Display the month name given a month number as follows: | |
| 208 | ||
| 209 | `r# month( 1 )` | |
| 210 | ||
| 211 | The preview pane shows: | |
| 212 | ||
| 213 | January | |
| 214 | ||
| 215 | ## Summary | |
| 216 | ||
| 217 | Authors can inline R statements into documents, directly, so long as those statements generate text. Plots, graphs, and images must be referenced as external image files or URLs. | |
| 1 | 218 |
| 1 | # ######################################################################## | |
| 2 | # | |
| 3 | # Substitute R expressions in a document with their evaluated value. The | |
| 4 | # anchor variable must be set for functions that use relative dates. | |
| 5 | # | |
| 6 | # ######################################################################## | |
| 7 | ||
| 8 | # Evaluates an expression; writes s if there is no expression. | |
| 9 | x <- function( s ) { | |
| 10 | return( | |
| 11 | tryCatch({ | |
| 12 | r = eval( parse( text=s ) ) | |
| 13 | ||
| 14 | # If the result isn't primitive, then it was probably parsed into | |
| 15 | # an unprintable object (e.g., "gray" becomes a colour). In those | |
| 16 | # cases, return the original text string. Otherwise, an atomic | |
| 17 | # value means a primitive type (string, integer, etc.) that can be | |
| 18 | # written directly into the document. | |
| 19 | # | |
| 20 | # See: http://stackoverflow.com/a/19501276/59087 | |
| 21 | if( is.atomic( r ) ) { | |
| 22 | r | |
| 23 | } | |
| 24 | else { | |
| 25 | s | |
| 26 | } | |
| 27 | }, | |
| 28 | warning = function( w ) { | |
| 29 | s | |
| 30 | }, | |
| 31 | error = function( e ) { | |
| 32 | s | |
| 33 | }) | |
| 34 | ) | |
| 35 | } | |
| 36 | ||
| 37 | # Returns a date offset by a given number of days, relative to the given | |
| 38 | # date (d). This does not use the anchor, but is used to get the anchor's | |
| 39 | # value as a date. | |
| 40 | when <- function( d, n = 0, format = "%Y-%m-%d" ) { | |
| 41 | as.Date( d, format = format ) + x( n ) | |
| 42 | } | |
| 43 | ||
| 44 | # Full date (s) offset by an optional number of days before or after. | |
| 45 | # This will remove leading zeros (applying leading spaces instead, which | |
| 46 | # are ignored by any worthwhile typesetting engine). | |
| 47 | annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) { | |
| 48 | format( when( anchor, days ), format = oformat ) | |
| 49 | } | |
| 50 | ||
| 51 | # Extracts the year from a date string. | |
| 52 | year <- function( days = 0, format = "%Y-%m-%d" ) { | |
| 53 | annal( days, format, "%Y" ) | |
| 54 | } | |
| 55 | ||
| 56 | # Day of the week (in days since the anchor date). | |
| 57 | weekday <- function( n ) { | |
| 58 | weekdays( when( anchor, n ) ) | |
| 59 | } | |
| 60 | ||
| 61 | # String concatenate function alias because paste0 is a terrible name. | |
| 62 | concat <- paste0 | |
| 63 | ||
| 64 | # Translates a number from digits to words using Chicago Manual of Style. | |
| 65 | # This does not translate numbers greater than one hundred. If ordinal | |
| 66 | # is TRUE, this will return the ordinal name. This will not produce ordinals | |
| 67 | # for numbers greater than 100 | |
| 68 | cms <- function( n, ordinal = FALSE ) { | |
| 69 | n <- x( n ) | |
| 70 | ||
| 71 | # We're done here. | |
| 72 | if( n == 0 ) { | |
| 73 | if( ordinal ) { | |
| 74 | return( "zeroth" ) | |
| 75 | } | |
| 76 | ||
| 77 | return( "zero" ) | |
| 78 | } | |
| 79 | ||
| 80 | # Concatenate this a little later. | |
| 81 | if( n < 0 ) { | |
| 82 | result = "negative " | |
| 83 | n = abs( n ) | |
| 84 | } | |
| 85 | ||
| 86 | # Do not spell out numbers greater than one hundred. | |
| 87 | if( n > 100 ) { | |
| 88 | # Comma-separated numbers. | |
| 89 | return( format( n, big.mark=",", trim=TRUE, scientific=FALSE ) ) | |
| 90 | } | |
| 91 | ||
| 92 | # Don't go beyond 100. | |
| 93 | if( n == 100 ) { | |
| 94 | if( ordinal ) { | |
| 95 | return( "one hundredth" ) | |
| 96 | } | |
| 97 | ||
| 98 | return( "one hundred" ) | |
| 99 | } | |
| 100 | ||
| 101 | # Samuel Langhorne Clemens noted English has too many exceptions. | |
| 102 | small = c( | |
| 103 | "one", "two", "three", "four", "five", | |
| 104 | "six", "seven", "eight", "nine", "ten", | |
| 105 | "eleven", "twelve", "thirteen", "fourteen", "fifteen", | |
| 106 | "sixteen", "seventeen", "eighteen", "nineteen" | |
| 107 | ) | |
| 108 | ||
| 109 | ord_small = c( | |
| 110 | "first", "second", "third", "fourth", "fifth", | |
| 111 | "sixth", "seventh", "eighth", "ninth", "tenth", | |
| 112 | "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", | |
| 113 | "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" | |
| 114 | ) | |
| 115 | ||
| 116 | # After this, the number (n) is between 20 and 99. | |
| 117 | if( n < 20 ) { | |
| 118 | if( ordinal ) { | |
| 119 | return( .subset( ord_small, n %% 100 ) ) | |
| 120 | } | |
| 121 | ||
| 122 | return( .subset( small, n %% 100 ) ) | |
| 123 | } | |
| 124 | ||
| 125 | tens = c( "", | |
| 126 | "twenty", "thirty", "forty", "fifty", | |
| 127 | "sixty", "seventy", "eighty", "ninety" | |
| 128 | ) | |
| 129 | ||
| 130 | ord_tens = c( "", | |
| 131 | "twentieth", "thirtieth", "fortieth", "fiftieth", | |
| 132 | "sixtieth", "seventieth", "eightieth", "ninetieth" | |
| 133 | ) | |
| 134 | ||
| 135 | ones_index = n %% 10 | |
| 136 | n = n %/% 10 | |
| 137 | ||
| 138 | # No number in the ones column, so the number must be a multiple of ten. | |
| 139 | if( ones_index == 0 ) { | |
| 140 | if( ordinal ) { | |
| 141 | return( .subset( ord_tens, n ) ) | |
| 142 | } | |
| 143 | ||
| 144 | return( .subset( tens, n ) ) | |
| 145 | } | |
| 146 | ||
| 147 | # Find the value from the ones column. | |
| 148 | if( ordinal ) { | |
| 149 | unit_1 = .subset( ord_small, ones_index ) | |
| 150 | } | |
| 151 | else { | |
| 152 | unit_1 = .subset( small, ones_index ) | |
| 153 | } | |
| 154 | ||
| 155 | # Find the tens column. | |
| 156 | unit_10 = .subset( tens, n ) | |
| 157 | ||
| 158 | # Hyphenate the tens and the ones together. | |
| 159 | concat( unit_10, concat( "-", unit_1 ) ) | |
| 160 | } | |
| 161 | ||
| 162 | # Returns a human-readable string that provides the elapsed time between | |
| 163 | # two numbers in terms of years, months, and days. If any unit value is zero, | |
| 164 | # the unit is not included. The words (year, month, day) are pluralized | |
| 165 | # according to English grammar. The numbers are written out according to | |
| 166 | # Chicago Manual of Style. This applies the serial comma. | |
| 167 | # | |
| 168 | # Both numbers are offsets relative to the anchor date. | |
| 169 | # | |
| 170 | # If all unit values are zero, this returns s ("same day" by default). | |
| 171 | # | |
| 172 | # If the start date (began) is greater than end date (ended), the dates are | |
| 173 | # swapped before calculations are performed. This allows any two dates | |
| 174 | # to be compared and positive unit values are always returned. | |
| 175 | # | |
| 176 | elapsed <- function( began, ended, s = "same day" ) { | |
| 177 | began = when( anchor, began ) | |
| 178 | ended = when( anchor, ended ) | |
| 179 | ||
| 180 | # Swap the dates if the end date comes before the start date. | |
| 181 | if( as.integer( ended - began ) < 0 ) { | |
| 182 | tempd = began | |
| 183 | began = ended | |
| 184 | ended = tempd | |
| 185 | } | |
| 186 | ||
| 187 | # Calculate number of elapsed years. | |
| 188 | years = length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 189 | ||
| 190 | # Move the start date up by the number of elapsed years. | |
| 191 | if( years > 0 ) { | |
| 192 | began = seq( began, length = 2, by = concat( years, " years" ) )[2] | |
| 193 | years = pl.numeric( "year", years ) | |
| 194 | } | |
| 195 | else { | |
| 196 | # Zero years. | |
| 197 | years = "" | |
| 198 | } | |
| 199 | ||
| 200 | # Calculate number of elapsed months, excluding years. | |
| 201 | months = length( seq( from = began, to = ended, by = 'month' ) ) - 1 | |
| 202 | ||
| 203 | # Move the start date up by the number of elapsed months | |
| 204 | if( months > 0 ) { | |
| 205 | began = seq( began, length = 2, by = concat( months, " months" ) )[2] | |
| 206 | months = pl.numeric( "month", months ) | |
| 207 | } | |
| 208 | else { | |
| 209 | # Zero months | |
| 210 | months = "" | |
| 211 | } | |
| 212 | ||
| 213 | # Calculate number of elapsed days, excluding months and years. | |
| 214 | days = length( seq( from = began, to = ended, by = 'day' ) ) - 1 | |
| 215 | ||
| 216 | if( days > 0 ) { | |
| 217 | days = pl.numeric( "day", days ) | |
| 218 | } | |
| 219 | else { | |
| 220 | # Zero days | |
| 221 | days = "" | |
| 222 | } | |
| 223 | ||
| 224 | if( years <= 0 && months <= 0 && days <= 0 ) { | |
| 225 | return( s ) | |
| 226 | } | |
| 227 | ||
| 228 | # Put them all in a vector, then remove the empty values. | |
| 229 | s <- c( years, months, days ) | |
| 230 | s <- s[ s != "" ] | |
| 231 | ||
| 232 | r <- paste( s, collapse = ", " ) | |
| 233 | ||
| 234 | # If all three items are present, replace the last comma with ", and". | |
| 235 | if( length( s ) > 2 ) { | |
| 236 | return( gsub( "(.*),", "\\1, and", r ) ) | |
| 237 | } | |
| 238 | ||
| 239 | # Does nothing if no commas are present. | |
| 240 | gsub( "(.*),", "\\1 and", r ) | |
| 241 | } | |
| 242 | ||
| 243 | # Returns the number (n) in English followed by the plural or singular | |
| 244 | # form of the given string (s; resumably a noun), if applicable, according | |
| 245 | # to English grammar. That is, pl.numeric( "wolf", 5 ) will return | |
| 246 | # "five wolves". | |
| 247 | pl.numeric <- function( s, n ) { | |
| 248 | concat( cms( n ), concat( " ", pluralize( s, n ) ) ) | |
| 249 | } | |
| 250 | ||
| 251 | # Name of the season, starting with an capital letter. | |
| 252 | season <- function( n, format = "%Y-%m-%d" ) { | |
| 253 | WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice | |
| 254 | SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox | |
| 255 | SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice | |
| 256 | AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox | |
| 257 | ||
| 258 | d <- when( anchor, n ) | |
| 259 | d <- as.Date( strftime( d, format="2016-%m-%d" ) ) | |
| 260 | ||
| 261 | ifelse( d >= WS | d < SE, "Winter", | |
| 262 | ifelse( d >= SE & d < SS, "Spring", | |
| 263 | ifelse( d >= SS & d < AE, "Summer", "Autumn" ) | |
| 264 | ) | |
| 265 | ) | |
| 266 | } | |
| 267 | ||
| 268 | # Converts the first letter in a string to lowercase | |
| 269 | lc <- function( s ) { | |
| 270 | concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 271 | } | |
| 272 | ||
| 273 | # Converts the first letter in a string to uppercase | |
| 274 | uc <- function( s ) { | |
| 275 | concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 276 | } | |
| 277 | ||
| 278 | # Returns the number of days between the given dates. | |
| 279 | days <- function( d1, d2, format = "%Y-%m-%d" ) { | |
| 280 | dates = c( d1, d2 ) | |
| 281 | dt = strptime( dates, format = format ) | |
| 282 | as.integer( difftime( dates[2], dates[1], units = "days" ) ) | |
| 283 | } | |
| 284 | ||
| 285 | # Returns the number of years elapsed. | |
| 286 | years <- function( began, ended ) { | |
| 287 | began = when( anchor, began ) | |
| 288 | ended = when( anchor, ended ) | |
| 289 | ||
| 290 | # Swap the dates if the end date comes before the start date. | |
| 291 | if( as.integer( ended - began ) < 0 ) { | |
| 292 | tempd = began | |
| 293 | began = ended | |
| 294 | ended = tempd | |
| 295 | } | |
| 296 | ||
| 297 | # Calculate number of elapsed years. | |
| 298 | length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 299 | } | |
| 300 | ||
| 301 | # Full name of the month, starting with a capital letter. | |
| 302 | month <- function( n ) { | |
| 303 | # Faster than month.name[ x( n ) ] | |
| 304 | .subset( month.name, x( n ) ) | |
| 305 | } | |
| 306 | ||
| 307 | money <- function( n ) { | |
| 308 | formatC( x( n ), format="d" ) | |
| 309 | } | |
| 1 | 310 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # Converts CSV to Markdown. | |
| 29 | # | |
| 30 | # ###################################################################### | |
| 31 | ||
| 32 | # Reads a CSV file and converts the contents to a Markdown table. The | |
| 33 | # file must be in the working directory as specified by setwd. | |
| 34 | # | |
| 35 | # @param f The filename to convert. | |
| 36 | # @param decimals Rounded decimal places (default 1). | |
| 37 | # @param totals Include total sums (default TRUE). | |
| 38 | # @param align Right-align numbers (default TRUE). | |
| 39 | csv2md <- function( f, decimals = 1, totals = T, align = T ) { | |
| 40 | # Read the CVS data from the file; ensure strings become characters. | |
| 41 | df <- read.table( f, sep=',', header=T, stringsAsFactors=F ) | |
| 42 | ||
| 43 | if( totals ) { | |
| 44 | # Determine what columns can be summed. | |
| 45 | number <- which( unlist( lapply( df, is.numeric ) ) ) | |
| 46 | ||
| 47 | # Use colSums when more than one summable column exists. | |
| 48 | if( length( number ) > 1 ) { | |
| 49 | f.sum <- colSums | |
| 50 | } | |
| 51 | else { | |
| 52 | f.sum <- sum | |
| 53 | } | |
| 54 | ||
| 55 | # Calculate the sum of all the summable columns and insert the | |
| 56 | # results back into the data frame. | |
| 57 | df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE ) | |
| 58 | ||
| 59 | # pluralize would be heavyweight here. | |
| 60 | if( length( number ) > 1 ) { | |
| 61 | t <- "**Totals**" | |
| 62 | } | |
| 63 | else { | |
| 64 | t <- "**Total**" | |
| 65 | } | |
| 66 | ||
| 67 | # Change the first column of the last line to "Total(s)". | |
| 68 | df[ nrow( df ), 1 ] <- t | |
| 69 | ||
| 70 | # Don't clutter the output with "NA" text. | |
| 71 | df[ is.na( df ) ] <- "" | |
| 72 | } | |
| 73 | ||
| 74 | if( align ) { | |
| 75 | is.char <- vapply( df, is.character, logical( 1 ) ) | |
| 76 | dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' ) | |
| 77 | } | |
| 78 | else { | |
| 79 | dashes <- paste( rep( '---', length( df ) ), collapse = '|') | |
| 80 | } | |
| 81 | ||
| 82 | # Create a Markdown version of the data frame. | |
| 83 | paste( | |
| 84 | paste( names( df ), collapse = '|'), '\n', | |
| 85 | dashes, '\n', | |
| 86 | paste( | |
| 87 | Reduce( function( x, y ) { | |
| 88 | paste( x, format( y, digits = decimals ), sep = '|' ) | |
| 89 | }, df | |
| 90 | ), | |
| 91 | collapse = '|\n', sep='' | |
| 92 | ) | |
| 93 | ) | |
| 94 | } | |
| 95 | ||
| 1 | 96 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 29 | # http://goo.gl/oRL4MP | |
| 30 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 31 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 32 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 33 | # | |
| 34 | # ###################################################################### | |
| 35 | ||
| 36 | pluralize <- function( s, n ) { | |
| 37 | result <- s | |
| 38 | ||
| 39 | # Partial implementation of Conway's algorithm for nouns. | |
| 40 | if( n != 1 ) { | |
| 41 | if( pl.noninflective( s ) || | |
| 42 | pl.suffix( "fish", s ) || | |
| 43 | pl.suffix( "ois", s ) || | |
| 44 | pl.suffix( "sheep", s ) || | |
| 45 | pl.suffix( "deer", s ) || | |
| 46 | pl.suffix( "pox", s ) || | |
| 47 | pl.suffix( "[A-Z].*ese", s ) || | |
| 48 | pl.suffix( "itis", s ) ) { | |
| 49 | # 1. Retain non-inflective user-mapped noun as is. | |
| 50 | # 2. Retain non-inflective plural as is. | |
| 51 | result <- s | |
| 52 | } | |
| 53 | else if( pl.is.irregular.pl( s ) ) { | |
| 54 | # 4. Change irregular plurals based on mapping. | |
| 55 | result <- pl.irregular.pl( s ) | |
| 56 | } | |
| 57 | else if( pl.is.irregular.es( s ) ) { | |
| 58 | # x. From Shevek's Pluralizer | |
| 59 | result <- pl.inflect( s, "", "es" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "man", s ) ) { | |
| 62 | # 5. For -man, change -an to -en | |
| 63 | result <- pl.inflect( s, "an", "en" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 66 | # 5. For [lm]ouse, change -ouse to -ice | |
| 67 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "tooth", s ) ) { | |
| 70 | # 5. For -tooth, change -ooth to -eeth | |
| 71 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "goose", s ) ) { | |
| 74 | # 5. For -goose, change -oose to -eese | |
| 75 | result <- pl.inflect( s, "oose", "eese" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "foot", s ) ) { | |
| 78 | # 5. For -foot, change -oot to -eet | |
| 79 | result <- pl.inflect( s, "oot", "eet" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "zoon", s ) ) { | |
| 82 | # 5. For -zoon, change -on to -a | |
| 83 | result <- pl.inflect( s, "on", "a" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 86 | # 5. Change -cis, -sis, -xis to -es | |
| 87 | result <- pl.inflect( s, "is", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([cs]h|ss)", s ) ) { | |
| 90 | # 8. Change -ch, -sh, -ss to -es | |
| 91 | result <- pl.inflect( s, "", "es" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 94 | # 9. Change -f to -ves | |
| 95 | result <- pl.inflect( s, "f", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 98 | # 9. Change -fe to -ves | |
| 99 | result <- pl.inflect( s, "fe", "ves" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "([aeiou]y|[A-Z].*y)", s ) ) { | |
| 102 | # 10. Change -y to -ys. | |
| 103 | result <- pl.inflect( s, "", "s" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "y", s ) ) { | |
| 106 | # 10. Change -y to -ies. | |
| 107 | result <- pl.inflect( s, "y", "ies" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s. | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # Pluralize s if n is not equal to 1. | |
| 119 | pl <- function( s, n ) { | |
| 120 | pluralize( s, x( n ) ) | |
| 121 | } | |
| 122 | ||
| 123 | # Returns the given string (s) with its suffix replaced by r. | |
| 124 | pl.inflect <- function( s, suffix, r ) { | |
| 125 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 126 | } | |
| 127 | ||
| 128 | # Answers whether the given string (s) has the given ending. | |
| 129 | pl.suffix <- function( ending, s ) { | |
| 130 | grepl( paste( ending, "$", sep="" ), s ) | |
| 131 | } | |
| 132 | ||
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | pl.noninflective <- function( s ) { | |
| 135 | v <- c( | |
| 136 | "aircraft", "Bhutanese", "bison", "bream", "breeches", "britches", | |
| 137 | "Burmese", "carp", "chassis", "Chinese", "clippers", "cod", "contretemps", | |
| 138 | "corps", "debris", "diabetes", "djinn", "eland", "elk", "flounder", | |
| 139 | "fracas", "gallows", "graffiti", "headquarters", "herpes", "high-jinks", | |
| 140 | "homework", "hovercraft", "innings", "jackanapes", "Japanese", | |
| 141 | "Lebanese", "mackerel", "means", "measles", "mews", "mumps", "news", | |
| 142 | "pincers", "pliers", "Portuguese", "proceedings", "rabies", "salmon", | |
| 143 | "scissors", "sea-bass", "Senegalese", "series", "shears", "Siamese", | |
| 144 | "Sinhalese", "spacecraft", "species", "swine", "trout", "tuna", | |
| 145 | "Vietnamese", "watercraft", "whiting", "wildebeest" | |
| 146 | ) | |
| 147 | ||
| 148 | is.element( s, v ) | |
| 149 | } | |
| 150 | ||
| 151 | # Answers whether the given string (s) is an irregular plural. | |
| 152 | pl.is.irregular.pl <- function( s ) { | |
| 153 | # Could be refactored with pl.irregular.pl... | |
| 154 | v <- c( | |
| 155 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 156 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 157 | ) | |
| 158 | ||
| 159 | is.element( s, v ) | |
| 160 | } | |
| 161 | ||
| 162 | # Call to pluralize an irregular noun. Only call after confirming | |
| 163 | # the noun is irregular via pl.is.irregular.pl. | |
| 164 | pl.irregular.pl <- function( s ) { | |
| 165 | v <- list( | |
| 166 | "beef" = "beefs", | |
| 167 | "brother" = "brothers", | |
| 168 | "child" = "children", | |
| 169 | "cow" = "cows", | |
| 170 | "ephemeris" = "ephemerides", | |
| 171 | "genie" = "genies", | |
| 172 | "money" = "moneys", | |
| 173 | "mongoose" = "mongooses", | |
| 174 | "mythos" = "mythoi", | |
| 175 | "octopus" = "octopuses", | |
| 176 | "ox" = "oxen", | |
| 177 | "soliloquy" = "soliloquies", | |
| 178 | "trilby" = "trilbys" | |
| 179 | ) | |
| 180 | ||
| 181 | # Faster version of v[[ s ]] | |
| 182 | .subset2( v, s ) | |
| 183 | } | |
| 184 | ||
| 185 | # Answers whether the given string (s) pluralizes with -es. | |
| 186 | pl.is.irregular.es <- function( s ) { | |
| 187 | v <- c( | |
| 188 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 189 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 190 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 191 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 192 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 193 | ) | |
| 194 | ||
| 195 | is.element( s, v ) | |
| 196 | } | |
| 197 | ||
| 1 | 198 |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultOptions |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultSettings |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultSnitch |
| 1 | ||
| 1 | com.scrivenvar.service.events.impl.DefaultNotifier |
| 1 | app.properties | |
| 1 | 2 |
| 1 | #!/bin/bash | |
| 2 | ||
| 3 | INKSCAPE="/usr/bin/inkscape" | |
| 4 | PNG_COMPRESS="optipng" | |
| 5 | PNG_COMPRESS_OPTS="-o9 *png" | |
| 6 | ICO_TOOL="icotool" | |
| 7 | ICO_TOOL_OPTS="-c -o ../../../../../icons/logo.ico logo64.png" | |
| 8 | ||
| 9 | declare -a SIZES=("16" "32" "64" "128" "256" "512") | |
| 10 | ||
| 11 | for i in "${SIZES[@]}"; do | |
| 12 | # -y: export background opacity 0 | |
| 13 | $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png" | |
| 14 | done | |
| 15 | ||
| 16 | # Compess the PNG images. | |
| 17 | which $PNG_COMPRESS && $PNG_COMPRESS $PNG_COMPRESS_OPTS | |
| 18 | ||
| 19 | # Generate an ICO file. | |
| 20 | which $ICO_TOOL && $ICO_TOOL $ICO_TOOL_OPTS | |
| 21 | ||
| 1 | 22 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | .markdown-editor { | |
| 29 | -fx-font-size: 14px; | |
| 30 | } | |
| 31 | ||
| 32 | /*---- headers ----*/ | |
| 33 | ||
| 34 | .markdown-editor .h1 { -fx-font-size: 2.25em; } | |
| 35 | .markdown-editor .h2 { -fx-font-size: 1.75em; } | |
| 36 | .markdown-editor .h3 { -fx-font-size: 1.5em; } | |
| 37 | .markdown-editor .h4 { -fx-font-size: 1.25em; } | |
| 38 | .markdown-editor .h5 { -fx-font-size: 1.1em; } | |
| 39 | .markdown-editor .h6 { -fx-font-size: 1em; } | |
| 40 | ||
| 41 | .markdown-editor .h1, | |
| 42 | .markdown-editor .h2, | |
| 43 | .markdown-editor .h3, | |
| 44 | .markdown-editor .h4, | |
| 45 | .markdown-editor .h5, | |
| 46 | .markdown-editor .h6 { | |
| 47 | -fx-font-weight: bold; | |
| 48 | -fx-fill: derive(crimson, -20%); | |
| 49 | } | |
| 50 | ||
| 51 | ||
| 52 | /*---- inlines ----*/ | |
| 53 | ||
| 54 | .markdown-editor .strong { | |
| 55 | -fx-font-weight: bold; | |
| 56 | } | |
| 57 | ||
| 58 | .markdown-editor .em { | |
| 59 | -fx-font-style: italic; | |
| 60 | } | |
| 61 | ||
| 62 | .markdown-editor .del { | |
| 63 | -fx-strikethrough: true; | |
| 64 | } | |
| 65 | ||
| 66 | .markdown-editor .a { | |
| 67 | -fx-fill: #4183C4 !important; | |
| 68 | } | |
| 69 | ||
| 70 | .markdown-editor .img { | |
| 71 | -fx-fill: #4183C4 !important; | |
| 72 | } | |
| 73 | ||
| 74 | .markdown-editor .code { | |
| 75 | -fx-font-family: monospace; | |
| 76 | -fx-fill: #090 !important; | |
| 77 | } | |
| 78 | ||
| 79 | ||
| 80 | /*---- blocks ----*/ | |
| 81 | ||
| 82 | .markdown-editor .pre { | |
| 83 | -fx-font-family: monospace; | |
| 84 | -fx-fill: #060 !important; | |
| 85 | } | |
| 86 | ||
| 87 | .markdown-editor .blockquote { | |
| 88 | -fx-fill: #777; | |
| 89 | } | |
| 90 | ||
| 91 | ||
| 92 | /*---- lists ----*/ | |
| 93 | ||
| 94 | .markdown-editor .ul { | |
| 95 | } | |
| 96 | ||
| 97 | .markdown-editor .ol { | |
| 98 | } | |
| 99 | ||
| 100 | .markdown-editor .li { | |
| 101 | -fx-fill: #444; | |
| 102 | } | |
| 103 | ||
| 104 | .markdown-editor .dl { | |
| 105 | } | |
| 106 | ||
| 107 | .markdown-editor .dt { | |
| 108 | -fx-font-weight: bold; | |
| 109 | -fx-font-style: italic; | |
| 110 | } | |
| 111 | ||
| 112 | .markdown-editor .dd { | |
| 113 | -fx-fill: #444; | |
| 114 | } | |
| 115 | ||
| 116 | ||
| 117 | /*---- table ----*/ | |
| 118 | ||
| 119 | .markdown-editor .table { | |
| 120 | -fx-font-family: monospace; | |
| 121 | } | |
| 122 | ||
| 123 | .markdown-editor .thead { | |
| 124 | } | |
| 125 | ||
| 126 | .markdown-editor .tbody { | |
| 127 | } | |
| 128 | ||
| 129 | .markdown-editor .caption { | |
| 130 | } | |
| 131 | ||
| 132 | .markdown-editor .th { | |
| 133 | -fx-font-weight: bold; | |
| 134 | } | |
| 135 | ||
| 136 | .markdown-editor .tr { | |
| 137 | } | |
| 138 | ||
| 139 | .markdown-editor .td { | |
| 140 | } | |
| 141 | ||
| 142 | ||
| 143 | /*---- misc ----*/ | |
| 144 | ||
| 145 | .markdown-editor .html { | |
| 146 | -fx-font-family: monospace; | |
| 147 | -fx-fill: derive(crimson, -50%); | |
| 148 | } | |
| 149 | .markdown-editor .monospace { | |
| 150 | -fx-font-family: monospace; | |
| 151 | } | |
| 1 | 152 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | |
| 3 | ||
| 4 | <svg | |
| 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 6 | xmlns:cc="http://creativecommons.org/ns#" | |
| 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 8 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 9 | xmlns="http://www.w3.org/2000/svg" | |
| 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 12 | id="svg2" | |
| 13 | version="1.1" | |
| 14 | inkscape:version="0.91 r13725" | |
| 15 | width="512" | |
| 16 | height="512" | |
| 17 | viewBox="0 0 512 512" | |
| 18 | sodipodi:docname="logo.svg"> | |
| 19 | <metadata | |
| 20 | id="metadata8"> | |
| 21 | <rdf:RDF> | |
| 22 | <cc:Work | |
| 23 | rdf:about=""> | |
| 24 | <dc:format>image/svg+xml</dc:format> | |
| 25 | <dc:type | |
| 26 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 27 | <dc:title></dc:title> | |
| 28 | </cc:Work> | |
| 29 | </rdf:RDF> | |
| 30 | </metadata> | |
| 31 | <defs | |
| 32 | id="defs6" /> | |
| 33 | <sodipodi:namedview | |
| 34 | pagecolor="#ffffff" | |
| 35 | bordercolor="#666666" | |
| 36 | borderopacity="1" | |
| 37 | objecttolerance="10" | |
| 38 | gridtolerance="10" | |
| 39 | guidetolerance="10" | |
| 40 | inkscape:pageopacity="0" | |
| 41 | inkscape:pageshadow="2" | |
| 42 | inkscape:window-width="640" | |
| 43 | inkscape:window-height="480" | |
| 44 | id="namedview4" | |
| 45 | showgrid="false" | |
| 46 | fit-margin-top="0" | |
| 47 | fit-margin-left="0" | |
| 48 | fit-margin-right="0" | |
| 49 | fit-margin-bottom="0" | |
| 50 | inkscape:zoom="1.2682274" | |
| 51 | inkscape:cx="15.646213" | |
| 52 | inkscape:cy="213.34955" | |
| 53 | inkscape:current-layer="svg2" /> | |
| 54 | <path | |
| 55 | style="fill:#ce6200;fill-opacity:1" | |
| 56 | d="m 203.2244,511.85078 c -60.01827,-1.2968 -121.688643,-6.5314 -192.436493,-16.334 -5.8078027,-0.8047 -10.66110747,-1.561 -10.78511762,-1.6806 -0.12404567,-0.1196 3.90488112,-4.5812 8.95313512,-9.9147 32.9484785,-34.8102 70.4314485,-73.8923 104.1521555,-108.5956 l 11.87611,-12.2221 5.48905,-10.2177 c 35.82801,-66.6927 75.13064,-128.5665 105.90637,-166.7277 6.13805,-7.611 10.21451,-12.0689 17.28719,-18.9048 36.6818,-35.4537 108.27279,-83.724003 206.0323,-138.917303 22.10365,-12.47935 51.93386,-28.64995037 52.26391,-28.33165037 0.38883,0.37499 -2.35932,25.95575037 -4.86585,45.29275037 -7.28943,56.236403 -17.04619,103.128903 -28.07642,134.939803 -7.19617,20.7536 -14.81287,35.152 -22.9667,43.4155 -3.60444,3.6529 -6.58328,5.7941 -10.1313,7.2825 l -2.56414,1.0756 -53.43164,0.1713 -53.43166,0.1713 3.69973,1.8547 c 26.78565,13.4282 52.58051,27.5241 59.57122,32.5533 4.48397,3.2259 4.41278,2.9854 1.59124,5.3784 -26.99514,22.8955 -74.52961,44.0013 -140.23089,62.2641 -26.34995,7.3244 -57.85469,14.6842 -86.99871,20.3237 l -10.26943,1.9871 -52.01052,53.2733 -52.010524,53.2732 -29.459801,15.1165 c -26.4100885,13.5517 -29.3446639,15.1388 -28.347645,15.3311 0.6117029,0.118 4.0894221,0.2188 7.7282726,0.2239 3.6388854,0.01 16.1273694,0.2329 27.7522124,0.5059 51.576376,1.2116 146.083985,1.512 170.154295,0.5409 34.66996,-1.3988 52.7606,-2.9325 67.58258,-5.7293 2.68664,-0.507 4.82907,-0.9755 4.76094,-1.0412 -0.0681,-0.066 -3.24733,-0.8833 -7.0649,-1.8169 -8.04133,-1.9664 -25.10167,-5.3107 -41.1231,-8.0612 -47.6405,-8.1787 -65.48708,-12.0107 -74.13028,-15.9169 -3.90548,-1.7651 -7.13816,-4.7659 -8.12937,-7.5463 -1.01822,-2.8562 -0.92214,-6.5271 0.23315,-8.9083 1.86563,-3.8451 6.14837,-6.7199 12.26745,-8.2345 16.96993,-4.2004 57.27977,-6.1832 90.36228,-4.4448 54.7332,2.8761 117.0767,13.1228 178.50212,29.3385 18.03514,4.7611 51.66065,14.656 51.22677,15.0744 -0.0824,0.08 -5.72762,-0.854 -12.54488,-2.0745 -40.1043,-7.18 -60.50854,-10.2888 -101.40822,-15.4507 -24.4851,-3.0902 -55.12614,-5.9915 -77.58876,-7.3465 -26.58826,-1.6039 -61.15821,-1.7754 -80.99202,-0.4019 l -3.19705,0.2214 8.70308,1.4934 c 51.89698,8.9047 77.51746,14.9877 88.00479,20.8948 6.9134,3.894 10.30497,9.4381 9.33333,15.2569 -1.50397,9.0066 -10.51381,14.0257 -32.00273,17.8278 -16.31374,2.8863 -47.27575,4.3845 -77.23553,3.7371 z" | |
| 57 | id="path4138" /> | |
| 58 | <path | |
| 59 | style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" | |
| 60 | d="m 214.76931,324.51908 c 60.83777,-14.1145 111.89562,-31.6251 144.40025,-49.5229 3.12602,-1.7213 5.81747,-3.2537 5.98106,-3.4054 0.40534,-0.3759 -13.76388,-7.9415 -34.63489,-18.4929 -7.52161,-3.8026 -9.82337,-5.3787 -12.0735,-8.2668 -5.14485,-6.6036 -5.96081,-14.8404 -2.20331,-22.2417 1.80288,-3.5512 5.69484,-7.3007 9.36158,-9.019 5.20851,-2.4407 1.18148,-2.2865 59.71223,-2.2865 l 52.81361,0 2.13233,-2.1984 c 2.78673,-2.8731 5.23414,-6.4981 8.23035,-12.1905 14.14966,-26.8827 26.71842,-78.3816 36.24347,-148.503303 0.76704,-5.6468 1.36194,-10.2983 1.32201,-10.3369 -0.0399,-0.038 -5.47754,2.9629 -12.08361,6.6697 l -12.01104,6.7396 -133.83068,137.037303 c -73.60688,75.3705 -134.81732,138.0567 -136.0232,139.3026 l -2.19251,2.2653 8.254,-1.8067 c 4.53969,-0.9937 12.01053,-2.6783 16.60185,-3.7435 z" | |
| 61 | id="path4136" /> | |
| 62 | <path | |
| 63 | style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" | |
| 64 | d="m 202.72524,284.43588 c 69.93294,-70.1332 135.4799,-131.9279 213.46406,-201.244203 7.71421,-6.8568 14.50542,-12.9341 15.09155,-13.5052 0.9482,-0.9239 0.96778,-0.9811 0.17761,-0.5188 -77.96496,45.611803 -139.23519,88.710503 -166.72539,117.278203 -18.81811,19.5556 -50.35654,64.861 -80.96704,116.3104 -0.91787,1.5427 1.02249,-0.3323 18.95921,-18.3204 z" | |
| 65 | id="path4142" /> | |
| 66 | <path | |
| 67 | style="fill:#000000" | |
| 68 | d="" | |
| 69 | id="path4140" | |
| 70 | inkscape:connector-curvature="0" /> | |
| 71 | </svg> | |
| 1 | 72 |
| 1 | # ######################################################################## | |
| 2 | # Main Application Window | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | # The application title should exist only once in the entire code base. | |
| 6 | # All other references should either refer to this value via the Messages | |
| 7 | # class, or indirectly using ${Main.title}. | |
| 8 | Main.title=Scrivenvar | |
| 9 | ||
| 10 | Main.menu.file=_File | |
| 11 | Main.menu.file.new=_New | |
| 12 | Main.menu.file.open=_Open... | |
| 13 | Main.menu.file.close=_Close | |
| 14 | Main.menu.file.close_all=Close All | |
| 15 | Main.menu.file.save=_Save | |
| 16 | Main.menu.file.save_as=Save _As | |
| 17 | Main.menu.file.save_all=Save A_ll | |
| 18 | Main.menu.file.exit=E_xit | |
| 19 | ||
| 20 | Main.menu.edit=_Edit | |
| 21 | Main.menu.edit.undo=_Undo | |
| 22 | Main.menu.edit.redo=_Redo | |
| 23 | Main.menu.edit.find=_Find | |
| 24 | Main.menu.edit.find.next=Find _Next | |
| 25 | Main.menu.edit.preferences=_Preferences | |
| 26 | ||
| 27 | Main.menu.insert=_Insert | |
| 28 | Main.menu.insert.bold=Bold | |
| 29 | Main.menu.insert.italic=Italic | |
| 30 | Main.menu.insert.superscript=Superscript | |
| 31 | Main.menu.insert.subscript=Subscript | |
| 32 | Main.menu.insert.strikethrough=Strikethrough | |
| 33 | Main.menu.insert.blockquote=Blockquote | |
| 34 | Main.menu.insert.code=Inline Code | |
| 35 | Main.menu.insert.fenced_code_block=Fenced Code Block | |
| 36 | Main.menu.insert.fenced_code_block.prompt=Enter code here | |
| 37 | Main.menu.insert.link=Link... | |
| 38 | Main.menu.insert.image=Image... | |
| 39 | Main.menu.insert.header.1=Header 1 | |
| 40 | Main.menu.insert.header.1.prompt=header 1 | |
| 41 | Main.menu.insert.header.2=Header 2 | |
| 42 | Main.menu.insert.header.2.prompt=header 2 | |
| 43 | Main.menu.insert.header.3=Header 3 | |
| 44 | Main.menu.insert.header.3.prompt=header 3 | |
| 45 | Main.menu.insert.unordered_list=Unordered List | |
| 46 | Main.menu.insert.ordered_list=Ordered List | |
| 47 | Main.menu.insert.horizontal_rule=Horizontal Rule | |
| 48 | ||
| 49 | Main.menu.help=_Help | |
| 50 | Main.menu.help.about=About ${Main.title} | |
| 51 | ||
| 52 | # ######################################################################## | |
| 53 | # Status Bar | |
| 54 | # ######################################################################## | |
| 55 | ||
| 56 | Main.statusbar.text.offset=offset | |
| 57 | Main.statusbar.line=Line {0} of {1}, ${Main.statusbar.text.offset} {2} | |
| 58 | Main.statusbar.state.default=OK | |
| 59 | Main.statusbar.parse.error={0} (near ${Main.statusbar.text.offset} {1}) | |
| 60 | ||
| 61 | # ######################################################################## | |
| 62 | # Preferences | |
| 63 | # ######################################################################## | |
| 64 | ||
| 65 | Preferences.r=R | |
| 66 | Preferences.r.script=Startup Script | |
| 67 | Preferences.r.script.desc=Script runs prior to executing R statements within the document. | |
| 68 | Preferences.r.directory=Working Directory | |
| 69 | Preferences.r.directory.desc=Value assigned to $application.r.working.directory$ and usable in the startup script. | |
| 70 | ||
| 71 | Preferences.images=Images | |
| 72 | Preferences.images.directory=Relative Directory | |
| 73 | Preferences.images.directory.desc=Path prepended to embedded images referenced using local file paths. | |
| 74 | Preferences.images.suffixes=Extensions | |
| 75 | Preferences.images.suffixes.desc=Preferred order of image file types to embed, separated by spaces. | |
| 76 | ||
| 77 | Preferences.definitions=Definitions | |
| 78 | Preferences.definitions.path=File name | |
| 79 | Preferences.definitions.path.desc=Absolute path to interpolated string definitions. | |
| 80 | ||
| 81 | # ######################################################################## | |
| 82 | # Definition Pane and its Tree View | |
| 83 | # ######################################################################## | |
| 84 | ||
| 85 | Definition.menu.create=Create | |
| 86 | Definition.menu.rename=Rename | |
| 87 | Definition.menu.remove=Delete | |
| 88 | Definition.menu.add.default=Undefined | |
| 89 | ||
| 90 | # ######################################################################## | |
| 91 | # Failure messages with respect to YAML files. | |
| 92 | # ######################################################################## | |
| 93 | yaml.error.open=Could not open YAML file (ensure non-empty file). | |
| 94 | yaml.error.unresolvable=Too much indirection for: ''{0}'' = ''{1}''. | |
| 95 | yaml.error.missing=Empty definition value for key ''{0}''. | |
| 96 | yaml.error.tree.form=Unassigned definition near ''{0}''. | |
| 97 | ||
| 98 | # ######################################################################## | |
| 99 | # File Editor | |
| 100 | # ######################################################################## | |
| 101 | ||
| 102 | FileEditor.loadFailed.message=Failed to load ''{0}''.\n\nReason: {1} | |
| 103 | FileEditor.loadFailed.title=Load | |
| 104 | FileEditor.loadFailed.reason.permissions=File must be readable and writable. | |
| 105 | FileEditor.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1} | |
| 106 | FileEditor.saveFailed.title=Save | |
| 107 | ||
| 108 | # ######################################################################## | |
| 109 | # File Open | |
| 110 | # ######################################################################## | |
| 111 | ||
| 112 | Dialog.file.choose.open.title=Open File | |
| 113 | Dialog.file.choose.save.title=Save File | |
| 114 | ||
| 115 | Dialog.file.choose.filter.title.source=Source Files | |
| 116 | Dialog.file.choose.filter.title.definition=Definition Files | |
| 117 | Dialog.file.choose.filter.title.xml=XML Files | |
| 118 | Dialog.file.choose.filter.title.all=All Files | |
| 119 | ||
| 120 | # ######################################################################## | |
| 121 | # Alert Dialog | |
| 122 | # ######################################################################## | |
| 123 | ||
| 124 | Alert.file.close.title=Close | |
| 125 | Alert.file.close.text=Save changes to {0}? | |
| 126 | ||
| 127 | # ######################################################################## | |
| 128 | # Definition Pane | |
| 129 | # ######################################################################## | |
| 130 | ||
| 131 | Pane.definition.node.root.title=Definitions | |
| 132 | ||
| 133 | # Controls ############################################################### | |
| 134 | ||
| 135 | # ######################################################################## | |
| 136 | # Browse Directory | |
| 137 | # ######################################################################## | |
| 138 | ||
| 139 | BrowseDirectoryButton.chooser.title=Browse for local folder | |
| 140 | BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title} | |
| 141 | ||
| 142 | # ######################################################################## | |
| 143 | # Browse File | |
| 144 | # ######################################################################## | |
| 145 | ||
| 146 | BrowseFileButton.chooser.title=Browse for local file | |
| 147 | BrowseFileButton.chooser.allFilesFilter=All Files | |
| 148 | BrowseFileButton.tooltip=${BrowseFileButton.chooser.title} | |
| 149 | ||
| 150 | # Dialogs ################################################################ | |
| 151 | ||
| 152 | # ######################################################################## | |
| 153 | # Image | |
| 154 | # ######################################################################## | |
| 155 | ||
| 156 | Dialog.image.title=Image | |
| 157 | Dialog.image.chooser.imagesFilter=Images | |
| 158 | Dialog.image.previewLabel.text=Markdown Preview\: | |
| 159 | Dialog.image.textLabel.text=Alternate Text\: | |
| 160 | Dialog.image.titleLabel.text=Title (tooltip)\: | |
| 161 | Dialog.image.urlLabel.text=Image URL\: | |
| 162 | ||
| 163 | # ######################################################################## | |
| 164 | # Hyperlink | |
| 165 | # ######################################################################## | |
| 166 | ||
| 167 | Dialog.link.title=Link | |
| 168 | Dialog.link.previewLabel.text=Markdown Preview\: | |
| 169 | Dialog.link.textLabel.text=Link Text\: | |
| 170 | Dialog.link.titleLabel.text=Title (tooltip)\: | |
| 171 | Dialog.link.urlLabel.text=Link URL\: | |
| 172 | ||
| 173 | # ######################################################################## | |
| 174 | # About | |
| 175 | # ######################################################################## | |
| 176 | ||
| 177 | Dialog.about.title=About | |
| 178 | Dialog.about.header=${Main.title} | |
| 179 | Dialog.about.content=Copyright 2020 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber | |
| 180 | ||
| 181 | # Options ################################################################ | |
| 182 | ||
| 183 | # ######################################################################## | |
| 184 | # Options Dialog | |
| 185 | # ######################################################################## | |
| 186 | ||
| 187 | OptionsDialog.title=Options | |
| 188 | OptionsDialog.generalTab.text=General | |
| 189 | OptionsDialog.markdownTab.text=Markdown | |
| 190 | ||
| 191 | # ######################################################################## | |
| 192 | # General Options Pane | |
| 193 | # ######################################################################## | |
| 194 | ||
| 195 | GeneralOptionsPane.encodingLabel.text=En_coding\: | |
| 196 | GeneralOptionsPane.lineSeparatorLabel.text=_Line separator\: | |
| 197 | GeneralOptionsPane.lineSeparatorLabel2.text=(applies to new files only) | |
| 198 | ||
| 199 | GeneralOptionsPane.platformDefault=Platform Default ({0}) | |
| 200 | GeneralOptionsPane.sepWindows=Windows (CRLF) | |
| 201 | GeneralOptionsPane.sepUnix=Unix (LF) | |
| 202 | ||
| 203 | # ######################################################################## | |
| 204 | # Markdown Options Pane | |
| 205 | # ######################################################################## | |
| 206 | ||
| 207 | MarkdownOptionsPane.abbreviationsExtCheckBox.text=A_bbreviations in the way of | |
| 208 | MarkdownOptionsPane.abbreviationsExtLink.text=Markdown Extra | |
| 209 | MarkdownOptionsPane.anchorlinksExtCheckBox.text=_Anchor links in headers | |
| 210 | MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text=Requires a space char after Atx \# header prefixes, so that \#dasdsdaf is not a header | |
| 211 | MarkdownOptionsPane.autolinksExtCheckBox.text=_Plain (undelimited) autolinks in the way of | |
| 212 | MarkdownOptionsPane.autolinksExtLink.text=Github-flavoured-Markdown | |
| 213 | MarkdownOptionsPane.definitionListsExtCheckBox.text=_Definition lists in the way of | |
| 214 | MarkdownOptionsPane.definitionListsExtLink.text=Markdown Extra | |
| 215 | MarkdownOptionsPane.extAnchorLinksExtCheckBox.text=Generate anchor links for headers using complete contents of the header | |
| 216 | MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text=_Fenced Code Blocks in the way of | |
| 217 | MarkdownOptionsPane.fencedCodeBlocksExtLabel.text=or | |
| 218 | MarkdownOptionsPane.fencedCodeBlocksExtLink.text=Markdown Extra | |
| 219 | MarkdownOptionsPane.fencedCodeBlocksExtLink2.text=Github-flavoured-Markdown | |
| 220 | MarkdownOptionsPane.forceListItemParaExtCheckBox.text=Force List and Definition Paragraph wrapping if it includes more than just a single paragraph | |
| 221 | MarkdownOptionsPane.hardwrapsExtCheckBox.text=_Newlines in paragraph-like content as real line breaks, see | |
| 222 | MarkdownOptionsPane.hardwrapsExtLink.text=Github-flavoured-Markdown | |
| 223 | MarkdownOptionsPane.quotesExtCheckBox.text=Beautify single _quotes, double quotes and double angle quotes (\u00ab and \u00bb) | |
| 224 | MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text=Allow horizontal rules without a blank line following them | |
| 225 | MarkdownOptionsPane.smartsExtCheckBox.text=Beautify apostrophes, _ellipses ("..." and ". . .") and dashes ("--" and "---") | |
| 226 | MarkdownOptionsPane.strikethroughExtCheckBox.text=_Strikethrough | |
| 227 | MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text=Suppress the _output of HTML blocks | |
| 228 | MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text=Suppress the o_utput of inline HTML elements | |
| 229 | MarkdownOptionsPane.tablesExtCheckBox.text=_Tables similar to | |
| 230 | MarkdownOptionsPane.tablesExtLabel.text=(like | |
| 231 | MarkdownOptionsPane.tablesExtLabel2.text=tables, but with colspan support) | |
| 232 | MarkdownOptionsPane.tablesExtLink.text=MultiMarkdown | |
| 233 | MarkdownOptionsPane.tablesExtLink2.text=Markdown Extra | |
| 234 | MarkdownOptionsPane.taskListItemsExtCheckBox.text=GitHub style task list items | |
| 235 | MarkdownOptionsPane.wikilinksExtCheckBox.text=_Wiki-style links ("[[wiki link]]") | |
| 1 | 236 |
| 1 | /* | |
| 2 | This software is released under the MIT license: | |
| 3 | ||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | |
| 5 | this software and associated documentation files (the "Software"), to deal in | |
| 6 | the Software without restriction, including without limitation the rights to | |
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
| 8 | the Software, and to permit persons to whom the Software is furnished to do so, | |
| 9 | subject to the following conditions: | |
| 10 | ||
| 11 | The above copyright notice and this permission notice shall be included in all | |
| 12 | copies or substantial portions of the Software. | |
| 13 | ||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
| 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
| 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 20 | */ | |
| 21 | ||
| 22 | /* Source: https://github.com/nicolashery/markdownpad-github */ | |
| 23 | ||
| 24 | /* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */ | |
| 25 | ||
| 26 | /* RESET | |
| 27 | =============================================================================*/ | |
| 28 | ||
| 29 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, | |
| 30 | p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, | |
| 31 | em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, | |
| 32 | b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, | |
| 33 | legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, | |
| 34 | details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, | |
| 35 | ruby, section, summary, time, mark, audio, video { | |
| 36 | margin: 0; | |
| 37 | padding: 0; | |
| 38 | border: 0; | |
| 39 | } | |
| 40 | ||
| 41 | /* BODY | |
| 42 | =============================================================================*/ | |
| 43 | ||
| 44 | body { | |
| 45 | font-family: serif; | |
| 46 | font-size: 14px; | |
| 47 | line-height: 1.6; | |
| 48 | color: #333; | |
| 49 | background-color: #fff; | |
| 50 | padding: 20px; | |
| 51 | max-width: 960px; | |
| 52 | margin: 0 auto; | |
| 53 | } | |
| 54 | ||
| 55 | body>*:first-child { | |
| 56 | margin-top: 0 !important; | |
| 57 | } | |
| 58 | ||
| 59 | body>*:last-child { | |
| 60 | margin-bottom: 0 !important; | |
| 61 | } | |
| 62 | ||
| 63 | /* BLOCKS | |
| 64 | =============================================================================*/ | |
| 65 | ||
| 66 | p, blockquote, ul, ol, dl, table, pre { | |
| 67 | margin: 15px 0; | |
| 68 | } | |
| 69 | ||
| 70 | /* HEADERS | |
| 71 | =============================================================================*/ | |
| 72 | ||
| 73 | h1, h2, h3, h4, h5, h6 { | |
| 74 | margin: 20px 0 10px; | |
| 75 | padding: 0; | |
| 76 | font-weight: bold; | |
| 77 | -webkit-font-smoothing: antialiased; | |
| 78 | } | |
| 79 | ||
| 80 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, | |
| 81 | h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { | |
| 82 | font-size: inherit; | |
| 83 | } | |
| 84 | ||
| 85 | h1 { | |
| 86 | font-size: 28px; | |
| 87 | color: #000; | |
| 88 | } | |
| 89 | ||
| 90 | h2 { | |
| 91 | font-size: 24px; | |
| 92 | border-bottom: 1px solid #ccc; | |
| 93 | color: #000; | |
| 94 | } | |
| 95 | ||
| 96 | h3 { | |
| 97 | font-size: 18px; | |
| 98 | } | |
| 99 | ||
| 100 | h4 { | |
| 101 | font-size: 16px; | |
| 102 | } | |
| 103 | ||
| 104 | h5 { | |
| 105 | font-size: 14px; | |
| 106 | } | |
| 107 | ||
| 108 | h6 { | |
| 109 | color: #777; | |
| 110 | font-size: 14px; | |
| 111 | } | |
| 112 | ||
| 113 | body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, | |
| 114 | body>h3:first-child, body>h4:first-child, body>h5:first-child, | |
| 115 | body>h6:first-child { | |
| 116 | margin-top: 0; | |
| 117 | padding-top: 0; | |
| 118 | } | |
| 119 | ||
| 120 | a:first-child h1, a:first-child h2, a:first-child h3, | |
| 121 | a:first-child h4, a:first-child h5, a:first-child h6 { | |
| 122 | margin-top: 0; | |
| 123 | padding-top: 0; | |
| 124 | } | |
| 125 | ||
| 126 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { | |
| 127 | margin-top: 10px; | |
| 128 | } | |
| 129 | ||
| 130 | /* LINKS | |
| 131 | =============================================================================*/ | |
| 132 | ||
| 133 | a { | |
| 134 | color: #4183C4; | |
| 135 | text-decoration: none; | |
| 136 | } | |
| 137 | ||
| 138 | a:hover { | |
| 139 | text-decoration: underline; | |
| 140 | } | |
| 141 | ||
| 142 | /* LISTS | |
| 143 | =============================================================================*/ | |
| 144 | ||
| 145 | ul, ol { | |
| 146 | padding-left: 30px; | |
| 147 | } | |
| 148 | ||
| 149 | ul li > :first-child, | |
| 150 | ol li > :first-child, | |
| 151 | ul li ul:first-of-type, | |
| 152 | ol li ol:first-of-type, | |
| 153 | ul li ol:first-of-type, | |
| 154 | ol li ul:first-of-type { | |
| 155 | margin-top: 0px; | |
| 156 | } | |
| 157 | ||
| 158 | ul ul, ul ol, ol ol, ol ul { | |
| 159 | margin-bottom: 0; | |
| 160 | } | |
| 161 | ||
| 162 | dl { | |
| 163 | padding: 0; | |
| 164 | } | |
| 165 | ||
| 166 | dl dt { | |
| 167 | font-size: 14px; | |
| 168 | font-weight: bold; | |
| 169 | font-style: italic; | |
| 170 | padding: 0; | |
| 171 | margin: 15px 0 5px; | |
| 172 | } | |
| 173 | ||
| 174 | dl dt:first-child { | |
| 175 | padding: 0; | |
| 176 | } | |
| 177 | ||
| 178 | dl dt>:first-child { | |
| 179 | margin-top: 0px; | |
| 180 | } | |
| 181 | ||
| 182 | dl dt>:last-child { | |
| 183 | margin-bottom: 0px; | |
| 184 | } | |
| 185 | ||
| 186 | dl dd { | |
| 187 | margin: 0 0 15px; | |
| 188 | padding: 0 15px; | |
| 189 | } | |
| 190 | ||
| 191 | dl dd>:first-child { | |
| 192 | margin-top: 0px; | |
| 193 | } | |
| 194 | ||
| 195 | dl dd>:last-child { | |
| 196 | margin-bottom: 0px; | |
| 197 | } | |
| 198 | ||
| 199 | /* CODE | |
| 200 | =============================================================================*/ | |
| 201 | ||
| 202 | pre, code, tt { | |
| 203 | font-size: 12px; | |
| 204 | font-family: Consolas, "Liberation Mono", Courier, monospace; | |
| 205 | } | |
| 206 | ||
| 207 | code, tt { | |
| 208 | margin: 0 0px; | |
| 209 | padding: 0px 0px; | |
| 210 | white-space: nowrap; | |
| 211 | border: 1px solid #eaeaea; | |
| 212 | background-color: #f8f8f8; | |
| 213 | border-radius: 3px; | |
| 214 | } | |
| 215 | ||
| 216 | pre>code { | |
| 217 | margin: 0; | |
| 218 | padding: 0; | |
| 219 | white-space: pre; | |
| 220 | border: none; | |
| 221 | background: transparent; | |
| 222 | } | |
| 223 | ||
| 224 | pre { | |
| 225 | background-color: #f8f8f8; | |
| 226 | border: 1px solid #ccc; | |
| 227 | font-size: 13px; | |
| 228 | line-height: 19px; | |
| 229 | overflow: auto; | |
| 230 | padding: 6px 10px; | |
| 231 | border-radius: 3px; | |
| 232 | } | |
| 233 | ||
| 234 | pre code, pre tt { | |
| 235 | background-color: transparent; | |
| 236 | border: none; | |
| 237 | } | |
| 238 | ||
| 239 | kbd { | |
| 240 | -moz-border-bottom-colors: none; | |
| 241 | -moz-border-left-colors: none; | |
| 242 | -moz-border-right-colors: none; | |
| 243 | -moz-border-top-colors: none; | |
| 244 | background-color: #DDDDDD; | |
| 245 | background-image: linear-gradient(#F1F1F1, #DDDDDD); | |
| 246 | background-repeat: repeat-x; | |
| 247 | border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD; | |
| 248 | border-image: none; | |
| 249 | border-radius: 2px 2px 2px 2px; | |
| 250 | border-style: solid; | |
| 251 | border-width: 1px; | |
| 252 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; | |
| 253 | line-height: 10px; | |
| 254 | padding: 1px 4px; | |
| 255 | } | |
| 256 | ||
| 257 | /* QUOTES | |
| 258 | =============================================================================*/ | |
| 259 | ||
| 260 | blockquote { | |
| 261 | border-left: 4px solid #DDD; | |
| 262 | padding: 0 15px; | |
| 263 | color: #777; | |
| 264 | } | |
| 265 | ||
| 266 | blockquote>:first-child { | |
| 267 | margin-top: 0px; | |
| 268 | } | |
| 269 | ||
| 270 | blockquote>:last-child { | |
| 271 | margin-bottom: 0px; | |
| 272 | } | |
| 273 | ||
| 274 | /* HORIZONTAL RULES | |
| 275 | =============================================================================*/ | |
| 276 | ||
| 277 | hr { | |
| 278 | clear: both; | |
| 279 | margin: 15px 0; | |
| 280 | height: 0px; | |
| 281 | overflow: hidden; | |
| 282 | border: none; | |
| 283 | background: transparent; | |
| 284 | border-bottom: 4px solid #ddd; | |
| 285 | padding: 0; | |
| 286 | } | |
| 287 | ||
| 288 | /* TABLES | |
| 289 | =============================================================================*/ | |
| 290 | ||
| 291 | table th { | |
| 292 | font-weight: bold; | |
| 293 | } | |
| 294 | ||
| 295 | table th, table td { | |
| 296 | border: 1px solid #ccc; | |
| 297 | padding: 6px 13px; | |
| 298 | } | |
| 299 | ||
| 300 | table tr { | |
| 301 | border-top: 1px solid #ccc; | |
| 302 | background-color: #fff; | |
| 303 | } | |
| 304 | ||
| 305 | table tr:nth-child(2n) { | |
| 306 | background-color: #f8f8f8; | |
| 307 | } | |
| 308 | ||
| 309 | /* IMAGES | |
| 310 | =============================================================================*/ | |
| 311 | ||
| 312 | img { | |
| 313 | max-width: 100% | |
| 314 | } | |
| 1 | 315 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | /*---- toolbar ----*/ | |
| 29 | ||
| 30 | .tool-bar { | |
| 31 | -fx-spacing: 0; | |
| 32 | } | |
| 33 | ||
| 34 | .tool-bar .button { | |
| 35 | -fx-background-color: transparent; | |
| 36 | } | |
| 37 | ||
| 38 | .tool-bar .button:hover { | |
| 39 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color; | |
| 40 | -fx-color: -fx-hover-base; | |
| 41 | } | |
| 42 | ||
| 43 | .tool-bar .button:armed { | |
| 44 | -fx-color: -fx-pressed-base; | |
| 45 | } | |
| 1 | 46 |
| 1 | # ######################################################################## | |
| 2 | # Application | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | application.title=scrivenvar | |
| 6 | application.package=com/${application.title} | |
| 7 | application.messages= com.${application.title}.messages | |
| 8 | ||
| 9 | # Suppress multiple file modified notifications for one logical modification. | |
| 10 | # Given in milliseconds. | |
| 11 | application.watchdog.timeout=50 | |
| 12 | ||
| 13 | # ######################################################################## | |
| 14 | # Preferences | |
| 15 | # ######################################################################## | |
| 16 | ||
| 17 | preferences.root=com.${application.title} | |
| 18 | preferences.root.state=state | |
| 19 | preferences.root.options=options | |
| 20 | preferences.root.definition.source=definition.source | |
| 21 | ||
| 22 | # ######################################################################## | |
| 23 | # File and Path References | |
| 24 | # ######################################################################## | |
| 25 | file.stylesheet.scene=${application.package}/scene.css | |
| 26 | file.stylesheet.markdown=${application.package}/editor/markdown.css | |
| 27 | file.stylesheet.preview=webview.css | |
| 28 | file.stylesheet.xml=${application.package}/xml.css | |
| 29 | ||
| 30 | file.logo.16 =${application.package}/logo16.png | |
| 31 | file.logo.32 =${application.package}/logo32.png | |
| 32 | file.logo.128=${application.package}/logo128.png | |
| 33 | file.logo.256=${application.package}/logo256.png | |
| 34 | file.logo.512=${application.package}/logo512.png | |
| 35 | ||
| 36 | # Default file name when a new file is created. | |
| 37 | # This ensures that the file type can always be | |
| 38 | # discerned so that the correct type of variable | |
| 39 | # reference can be inserted. | |
| 40 | file.default=untitled.md | |
| 41 | file.definition.default=variables.yaml | |
| 42 | ||
| 43 | # ######################################################################## | |
| 44 | # File name Extensions | |
| 45 | # ######################################################################## | |
| 46 | ||
| 47 | # Comma-separated list of definition file name extensions. | |
| 48 | definition.file.ext.json=*.json | |
| 49 | definition.file.ext.toml=*.toml | |
| 50 | definition.file.ext.yaml=*.yml,*.yaml | |
| 51 | definition.file.ext.properties=*.properties,*.props | |
| 52 | ||
| 53 | # Comma-separated list of file name extensions. | |
| 54 | file.ext.rmarkdown=*.Rmd | |
| 55 | file.ext.rxml=*.Rxml | |
| 56 | file.ext.source=*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt,${file.ext.rmarkdown},${file.ext.rxml} | |
| 57 | file.ext.definition=${definition.file.ext.yaml} | |
| 58 | file.ext.xml=*.xml,${file.ext.rxml} | |
| 59 | file.ext.all=*.* | |
| 60 | ||
| 61 | # File name extension search order for images. | |
| 62 | file.ext.image.order=svg pdf png jpg tiff | |
| 63 | ||
| 64 | # ######################################################################## | |
| 65 | # Variable Name Editor | |
| 66 | # ######################################################################## | |
| 67 | ||
| 68 | # Maximum number of characters for a variable name. A variable is defined | |
| 69 | # as one or more non-whitespace characters up to this maximum length. | |
| 70 | editor.variable.maxLength=256 | |
| 71 | ||
| 72 | # ######################################################################## | |
| 73 | # Dialog Preferences | |
| 74 | # ######################################################################## | |
| 75 | ||
| 76 | dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R | |
| 77 | dialog.alert.button.order.linux=L_HE+UNYACBXIO_R | |
| 78 | dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R | |
| 79 | ||
| 80 | # Ensures a consistent button order for alert dialogs across platforms (because | |
| 81 | # the default button order on Linux defies all logic). | |
| 82 | dialog.alert.button.order=${dialog.alert.button.order.windows} | |
| 1 | 83 |
| 1 | --- | |
| 2 | c: | |
| 3 | protagonist: | |
| 4 | name: | |
| 5 | First: Chloe | |
| 6 | First_pos: $c.protagonist.name.First$'s | |
| 7 | Middle: Irene | |
| 8 | Family: Angelos | |
| 9 | nick: | |
| 10 | Father: Savant | |
| 11 | Mother: Sweetie | |
| 12 | colour: | |
| 13 | eyes: green | |
| 14 | hair: dark auburn | |
| 15 | syn_1: black | |
| 16 | syn_2: purple | |
| 17 | syn_11: teal | |
| 18 | syn_6: silver | |
| 19 | favourite: emerald green | |
| 20 | speech: | |
| 21 | tic: oh | |
| 22 | father: | |
| 23 | heritage: Greek | |
| 24 | name: | |
| 25 | Short: Bryce | |
| 26 | First: Bryson | |
| 27 | First_pos: $c.protagonist.father.name.First$'s | |
| 28 | Honourific: Mr. | |
| 29 | education: Masters | |
| 30 | vocation: | |
| 31 | name: robotics | |
| 32 | title: roboticist | |
| 33 | employer: | |
| 34 | name: | |
| 35 | Short: Rabota | |
| 36 | Full: $c.protagonist.father.employer.name.Short$ Designs | |
| 37 | hair: | |
| 38 | style: thick, curly | |
| 39 | colour: black | |
| 40 | eyes: | |
| 41 | colour: dark brown | |
| 42 | Endear: Dad | |
| 43 | vehicle: coupé | |
| 44 | mother: | |
| 45 | name: | |
| 46 | Short: Cass | |
| 47 | First: Cassandra | |
| 48 | First_pos: $c.protagonist.mother.name.First$'s | |
| 49 | Honourific: Mrs. | |
| 50 | education: PhD | |
| 51 | speech: | |
| 52 | tic: cute | |
| 53 | Honorific: Doctor | |
| 54 | vocation: | |
| 55 | article: an | |
| 56 | name: oceanography | |
| 57 | title: oceanographer | |
| 58 | employer: | |
| 59 | name: | |
| 60 | Full: Oregon State University | |
| 61 | Short: OSU | |
| 62 | eyes: | |
| 63 | colour: blue | |
| 64 | hair: | |
| 65 | style: thick, curly | |
| 66 | colour: dark brown | |
| 67 | Endear: Mom | |
| 68 | Endear_pos: Mom's | |
| 69 | uncle: | |
| 70 | name: | |
| 71 | First: Damian | |
| 72 | First_pos: $c.protagonist.uncle.name.First$'s | |
| 73 | Family: Moros | |
| 74 | hands: | |
| 75 | fingers: | |
| 76 | shape: long, bony | |
| 77 | friend: | |
| 78 | primary: | |
| 79 | name: | |
| 80 | First: Gerard | |
| 81 | First_pos: $c.protagonist.friend.primary.name.First$'s | |
| 82 | Family: Baran | |
| 83 | Family_pos: $c.protagonist.friend.primary.name.Family$'s | |
| 84 | favourite: | |
| 85 | colour: midnight blue | |
| 86 | eyes: | |
| 87 | colour: hazel | |
| 88 | mother: | |
| 89 | name: | |
| 90 | First: Isabella | |
| 91 | Short: Izzy | |
| 92 | Honourific: Mrs. | |
| 93 | father: | |
| 94 | name: | |
| 95 | Short: Mo | |
| 96 | First: Montgomery | |
| 97 | First_pos: $c.protagonist.friend.primary.father.name.First$'s | |
| 98 | Honourific: Mr. | |
| 99 | speech: | |
| 100 | tic: y'know | |
| 101 | endear: Pops | |
| 102 | military: | |
| 103 | primary: | |
| 104 | name: | |
| 105 | First: Felix | |
| 106 | Family: LeMay | |
| 107 | Family_pos: LeMay's | |
| 108 | rank: | |
| 109 | Short: General | |
| 110 | Full: Brigadier $c.military.primary.rank.Short$ | |
| 111 | colour: | |
| 112 | eyes: gray | |
| 113 | hair: dirty brown | |
| 114 | secondary: | |
| 115 | name: | |
| 116 | Family: Grell | |
| 117 | rank: Colonel | |
| 118 | colour: | |
| 119 | eyes: green | |
| 120 | hair: deep red | |
| 121 | quaternary: | |
| 122 | name: | |
| 123 | First: Gretchen | |
| 124 | Family: Steinherz | |
| 125 | minor: | |
| 126 | primary: | |
| 127 | name: | |
| 128 | First: River | |
| 129 | Family: Banks | |
| 130 | Honourific: Mx. | |
| 131 | vocation: | |
| 132 | title: salesperson | |
| 133 | employer: | |
| 134 | Name: Geophysical Prospecting Incorporated | |
| 135 | Abbr: GPI | |
| 136 | Area: Cold Spring Creek | |
| 137 | payment: twenty million | |
| 138 | secondary: | |
| 139 | name: | |
| 140 | First: Renato | |
| 141 | Middle: Carroña | |
| 142 | Family: Salvatierra | |
| 143 | Family_pos: $c.minor.secondary.name.Family$'s | |
| 144 | Full: $c.minor.secondary.name.First$ $c.minor.secondary.name.Middle$ Alejandro Gregorio Eduardo Salomón Vidal $c.minor.secondary.name.Family$ | |
| 145 | Honourific: Mister | |
| 146 | Honourific_sp: Señor | |
| 147 | vocation: | |
| 148 | title: detective | |
| 149 | tertiary: | |
| 150 | name: | |
| 151 | First: Robert | |
| 152 | Family: Hanssen | |
| 153 | ||
| 154 | ai: | |
| 155 | protagonist: | |
| 156 | name: | |
| 157 | first: yoky | |
| 158 | First: Yoky | |
| 159 | First_pos: $c.ai.protagonist.name.First$'s | |
| 160 | Family: Tsukuda | |
| 161 | id: 46692 | |
| 162 | persona: | |
| 163 | name: | |
| 164 | First: Hoshi | |
| 165 | First_pos: $c.ai.protagonist.persona.name.First$'s | |
| 166 | Family: Yamamoto | |
| 167 | Family_pos: $c.ai.protagonist.persona.name.Family$'s | |
| 168 | culture: Japanese-American | |
| 169 | ethnicity: Asian | |
| 170 | rank: Technical Sergeant | |
| 171 | speech: | |
| 172 | tic: okay | |
| 173 | first: | |
| 174 | Name: Prôtos | |
| 175 | Name_pos: Prôtos' | |
| 176 | age: | |
| 177 | actual: twenty-six weeks | |
| 178 | virtual: five years | |
| 179 | second: | |
| 180 | Name: Défteros | |
| 181 | third: | |
| 182 | Name: Trítos | |
| 183 | fourth: | |
| 184 | Name: Tétartos | |
| 185 | material: | |
| 186 | type: metal | |
| 187 | raw: ilmenite | |
| 188 | extract: ore | |
| 189 | name: | |
| 190 | short: titanium | |
| 191 | long: $c.ai.material.name.short$ dioxide | |
| 192 | Abbr: TiO~2~ | |
| 193 | pejorative: tin | |
| 194 | animal: | |
| 195 | protagonist: | |
| 196 | Name: Trufflers | |
| 197 | type: pig | |
| 198 | antagonist: | |
| 199 | name: coywolf | |
| 200 | Name: Coywolf | |
| 201 | plural: coywolves | |
| 202 | ||
| 203 | narrator: | |
| 204 | one: (by $c.protagonist.father.name.First$ $c.protagonist.name.Family$) | |
| 205 | two: (by $c.protagonist.mother.name.First$ $c.protagonist.name.Family$) | |
| 206 | ||
| 207 | military: | |
| 208 | name: | |
| 209 | Short: Agency | |
| 210 | Short_pos: $military.name.Short$'s | |
| 211 | plural: agencies | |
| 212 | machine: | |
| 213 | Name: Skopós | |
| 214 | Name_pos: $military.machine.Name$' | |
| 215 | Location: Arctic | |
| 216 | predictor: quantum chips | |
| 217 | land: | |
| 218 | name: | |
| 219 | Full: $military.name.Short$ of Defence | |
| 220 | Slogan: Safety in Numbers | |
| 221 | air: | |
| 222 | name: | |
| 223 | Full: $military.name.Short$ of Air | |
| 224 | compound: | |
| 225 | type: base | |
| 226 | lights: | |
| 227 | colour: blue | |
| 228 | nick: | |
| 229 | Prefix: Catacombs | |
| 230 | prep: of | |
| 231 | Suffix: Tartarus | |
| 232 | ||
| 233 | government: | |
| 234 | Country: United States | |
| 235 | ||
| 236 | location: | |
| 237 | protagonist: | |
| 238 | City: Corvallis | |
| 239 | Region: Oregon | |
| 240 | Geography: Willamette Valley | |
| 241 | secondary: | |
| 242 | City: Willow Branch Spring | |
| 243 | Region: Oregon | |
| 244 | Geography: Wheeler County | |
| 245 | Water: Clarno Rapids | |
| 246 | Road: Shaniko-Fossil Highway | |
| 247 | tertiary: | |
| 248 | City: Leavenworth | |
| 249 | Region: Washington | |
| 250 | Type: Bavarian village | |
| 251 | school: | |
| 252 | address: 1400 Northwest Buchanan Avenue | |
| 253 | hospital: | |
| 254 | Name: Good Samaritan Regional Medical Center | |
| 255 | ai: | |
| 256 | escape: | |
| 257 | country: | |
| 258 | Name: Ecuador | |
| 259 | Name_pos: Ecuador's | |
| 260 | mountain: | |
| 261 | Name: Chimborazo | |
| 262 | ||
| 263 | language: | |
| 264 | ai: | |
| 265 | article: an | |
| 266 | singular: exanimis | |
| 267 | plural: exanimēs | |
| 268 | brain: | |
| 269 | singular: superum | |
| 270 | plural: supera | |
| 271 | title: memristor array | |
| 272 | Title: Memristor Array | |
| 273 | police: | |
| 274 | slang: | |
| 275 | singular: mippo | |
| 276 | plural: $language.police.slang.singular$s | |
| 277 | ||
| 278 | date: | |
| 279 | anchor: 2042-09-02 | |
| 280 | protagonist: | |
| 281 | born: 0 | |
| 282 | conceived: -243 | |
| 283 | attacked: | |
| 284 | first: 2192 | |
| 285 | second: 8064 | |
| 286 | father: | |
| 287 | attacked: | |
| 288 | first: -8205 | |
| 289 | date: | |
| 290 | second: -1550 | |
| 291 | family: | |
| 292 | moved: | |
| 293 | first: $date.protagonist.conceived$ + 35 | |
| 294 | game: | |
| 295 | played: | |
| 296 | first: $date.protagonist.born$ - 672 | |
| 297 | second: $date.protagonist.family.moved.first$ + 2 | |
| 298 | ai: | |
| 299 | interviewed: 6198 | |
| 300 | onboarded: $date.ai.interviewed$ + 290 | |
| 301 | diagnosed: $date.ai.onboarded$ + 2 | |
| 302 | resigned: $date.ai.diagnosed$ + 3 | |
| 303 | trapped: $date.ai.resigned$ + 26 | |
| 304 | torturer: $date.ai.trapped$ + 18 | |
| 305 | memristor: $date.ai.torturer$ + 61 | |
| 306 | ethics: $date.ai.memristor$ + 415 | |
| 307 | trained: $date.ai.ethics$ + 385 | |
| 308 | mindjacked: $date.ai.trained$ + 22 | |
| 309 | bombed: $date.ai.mindjacked$ + 458 | |
| 310 | military: | |
| 311 | machine: | |
| 312 | Construction: Six years | |
| 313 | ||
| 314 | plot: | |
| 315 | Log: $c.ai.protagonist.name.First_pos$ Chronicles | |
| 316 | Channel: Quantum Channel | |
| 317 | ||
| 318 | device: | |
| 319 | computer: | |
| 320 | Name: Tau | |
| 321 | network: | |
| 322 | Name: Internet | |
| 323 | paper: | |
| 324 | name: | |
| 325 | full: electronic sheet | |
| 326 | short: sheet | |
| 327 | typewriter: | |
| 328 | Name: Underwood | |
| 329 | year: nineteen twenties | |
| 330 | room: root cellar | |
| 331 | portable: | |
| 332 | name: nanobook | |
| 333 | vehicle: | |
| 334 | name: robocars | |
| 335 | Name: Robocars | |
| 336 | sensor: | |
| 337 | name: BMP1580 | |
| 338 | phone: | |
| 339 | name: comm | |
| 340 | name_pos: $plot.device.phone.name$'s | |
| 341 | Name: Comm | |
| 342 | plural: $plot.device.phone.name$s | |
| 343 | video: | |
| 344 | name: vidfeed | |
| 345 | plural: $plot.device.video.name$s | |
| 346 | game: | |
| 347 | Name: Psynæris | |
| 348 | thought: transed | |
| 349 | machine: telecognos | |
| 350 | location: | |
| 351 | Building: Nijō Castle | |
| 352 | District: Gion | |
| 353 | City: Kyoto | |
| 354 | Country: Japan | |
| 355 | ||
| 356 | farm: | |
| 357 | population: | |
| 358 | estimate: 350 | |
| 359 | actual: 1,000 | |
| 360 | energy: 9800kJ | |
| 361 | width: 55m | |
| 362 | length: 55m | |
| 363 | storeys: 10 | |
| 364 | ||
| 365 | lamp: | |
| 366 | height: 0.17m | |
| 367 | length: 1.22m | |
| 368 | width: 0.28m | |
| 369 | ||
| 370 | crop: | |
| 371 | name: | |
| 372 | singular: tomato | |
| 373 | plural: $crop.name.singular$es | |
| 374 | energy: 318kJ | |
| 375 | weight: 450g | |
| 376 | yield: 50 | |
| 377 | harvests: 7 | |
| 378 | diameter: 2m | |
| 379 | height: 1.5m | |
| 380 | ||
| 381 | heading: | |
| 382 | ch_01: Till | |
| 383 | ch_02: Sow | |
| 384 | ch_03: Seed | |
| 385 | ch_04: Germinate | |
| 386 | ch_05: Grow | |
| 387 | ch_06: Shoot | |
| 388 | ch_07: Bud | |
| 389 | ch_08: Bloom | |
| 390 | ch_09: Pollinate | |
| 391 | ch_10: Fruit | |
| 392 | ch_11: Harvest | |
| 393 | ch_12: Deliver | |
| 394 | ch_13: Spoil | |
| 395 | ch_14: Revolt | |
| 396 | ch_15: Compost | |
| 397 | ch_16: Burn | |
| 398 | ch_17: Release | |
| 399 | ch_18: End Notes | |
| 400 | ch_19: Characters | |
| 401 | ||
| 402 | inference: | |
| 403 | unit: per cent | |
| 404 | min: two | |
| 405 | ch_sow: eighty | |
| 406 | ch_seed: fifty-two | |
| 407 | ch_germinate: thirty-one | |
| 408 | ch_grow: fifteen | |
| 409 | ch_shoot: seven | |
| 410 | ch_bloom: four | |
| 411 | ch_pollinate: two | |
| 412 | ch_harvest: ninety-five | |
| 413 | ch_delivery: ninety-eight | |
| 414 | ||
| 415 | link: | |
| 416 | tartarus: https://en.wikipedia.org/wiki/Tartarus | |
| 417 | exploits: https://www.google.ca/search?q=inurl:ftp+password+filetype:xls | |
| 418 | atalanta: https://en.wikipedia.org/wiki/Atalanta | |
| 419 | detain: https://goo.gl/RCNuOQ | |
| 420 | ceramics: https://en.wikipedia.org/wiki/Transparent_ceramics | |
| 421 | algernon: https://en.wikipedia.org/wiki/Flowers_for_Algernon | |
| 422 | holocaust: https://en.wikipedia.org/wiki/IBM_and_the_Holocaust | |
| 423 | memristor: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.404.9037\&rep=rep1\&type=pdf | |
| 424 | surveillance: https://www.youtube.com/watch?v=XEVlyP4_11M#t=1487 | |
| 425 | tor: https://www.torproject.org | |
| 426 | hydra: https://en.wikipedia.org/wiki/Lernaean_Hydra | |
| 427 | foliage: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3691134 | |
| 428 | drake: http://www.bbc.com/future/story/20120821-how-many-alien-worlds-exist | |
| 429 | fermi: https://arxiv.org/pdf/1404.0204v1.pdf | |
| 430 | face: https://www.youtube.com/watch?v=ladqJQLR2bA | |
| 431 | expenditures: http://wikipedia.org/wiki/List_of_countries_by_military_expenditures | |
| 432 | governance: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2003531 | |
| 433 | asimov: https://en.wikipedia.org/wiki/Three_Laws_of_Robotics | |
| 434 | clarke: https://en.wikipedia.org/wiki/Clarke's_three_laws | |
| 435 | jetpack: http://jetpackaviation.com/ | |
| 436 | hoverboard: https://www.youtube.com/watch?v=WQzLrvz4DKQ | |
| 437 | eyes_five: https://en.wikipedia.org/wiki/Five_Eyes | |
| 438 | eyes_nine: https://www.privacytools.io/ | |
| 439 | eyes_fourteen: http://electrospaces.blogspot.nl/2013/12/14-eyes-are-3rd-party-partners-forming.html | |
| 440 | tourism: http://www.spacefuture.com/archive/investigation_on_the_economic_and_technological_feasibiity_of_commercial_passenger_transportation_into_leo.shtml | |
| 441 | ||
| 1 | 442 |
| 1 | .tagmark { | |
| 2 | -fx-fill: gray; | |
| 3 | } | |
| 4 | .anytag { | |
| 5 | -fx-fill: crimson; | |
| 6 | } | |
| 7 | .paren { | |
| 8 | -fx-fill: firebrick; | |
| 9 | -fx-font-weight: bold; | |
| 10 | } | |
| 11 | .attribute { | |
| 12 | -fx-fill: darkviolet; | |
| 13 | } | |
| 14 | .avalue { | |
| 15 | -fx-fill: black; | |
| 16 | } | |
| 1 | 17 | |
| 18 | .comment { | |
| 19 | -fx-fill: teal; | |
| 20 | } |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | import org.junit.jupiter.api.Test; | |
| 32 | ||
| 33 | import static java.lang.String.format; | |
| 34 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 35 | ||
| 36 | public class TreeItemInterpolatorTest { | |
| 37 | ||
| 38 | private final static String AUTHOR_FIRST = "FirstName"; | |
| 39 | private final static String AUTHOR_LAST = "LastName"; | |
| 40 | private final static String AUTHOR_ALL = "$root.name.first$ $root.name.last$"; | |
| 41 | ||
| 42 | /** | |
| 43 | * Test that a hierarchical relationship of {@link TreeItem} instances can | |
| 44 | * create a flat map with all string values containing key names interpolated. | |
| 45 | */ | |
| 46 | @Test | |
| 47 | public void test_Resolve_ReferencesInTree_InterpolatedMap() { | |
| 48 | final var root = new TreeItem<>( "root" ); | |
| 49 | final var name = new TreeItem<>( "name" ); | |
| 50 | final var first = new TreeItem<>( "first" ); | |
| 51 | final var authorFirst = new TreeItem<>( AUTHOR_FIRST ); | |
| 52 | final var last = new TreeItem<>( "last" ); | |
| 53 | final var authorLast = new TreeItem<>( AUTHOR_LAST ); | |
| 54 | final var full = new TreeItem<>( "full" ); | |
| 55 | final var expr = new TreeItem<>( AUTHOR_ALL ); | |
| 56 | ||
| 57 | root.getChildren().add( name ); | |
| 58 | name.getChildren().add( first ); | |
| 59 | name.getChildren().add( last ); | |
| 60 | name.getChildren().add( full ); | |
| 61 | ||
| 62 | first.getChildren().add( authorFirst ); | |
| 63 | last.getChildren().add( authorLast ); | |
| 64 | full.getChildren().add( expr ); | |
| 65 | ||
| 66 | final var map = TreeItemAdapter.toMap( root ); | |
| 67 | ||
| 68 | var actualAuthor = map.get( "$root.name.full$" ); | |
| 69 | var expectedAuthor = AUTHOR_ALL; | |
| 70 | assertEquals( expectedAuthor, actualAuthor ); | |
| 71 | ||
| 72 | MapInterpolator.interpolate( map ); | |
| 73 | actualAuthor = map.get( "$root.name.full$" ); | |
| 74 | ||
| 75 | expectedAuthor = format( "%s %s", AUTHOR_FIRST, AUTHOR_LAST ); | |
| 76 | assertEquals( expectedAuthor, actualAuthor ); | |
| 77 | } | |
| 78 | } | |
| 1 | 79 |